Eliminating tail recursion for conditional types is ineffective

In TypeScript version 4.5, the feature of tail call optimization was introduced for recursive generics. The code snippet below calculates Fibonacci numbers (in unary) up to F12, but encounters an error when trying to compute F13 due to the "Type instantiation is excessively deep and possibly infinite" exception. This specific implementation of the Fibonacci function intentionally involves two non-tail-call positions for self-calling, serving as a demonstration.

The sole recursive function in this example is Run; the other functions (and references based on the interface) should not significantly impact the stack depth. What prevented TCO from working here, and how can it be resolved?

type Done<A> = { type: 'done', value: A };
type More<F, X> = { type: 'more', value: X, fn: F };
type FlatMap1<X, F> = { type: 'flatMap', value: X, fn: F }

// Interfaces and types omitted for brevity...

type R1 = Run<Fib<'1111111111111'>>

// Additional utilities also included in the original snippet...

Playground.

What happens when TCO works?

An equivalent JavaScript code snippet, purposely made complex, is capable of calculating even larger Fibonacci numbers (exceeding F35). However, this approach requires converting tail recursion into manual loop iteration and implementing binary number usage instead of unary. The primary limitation lies within the heap size, as the entire computation process has been trampolined. To learn more about this technique, refer to the articles linked above.

// JavaScript code for enhanced Fibonacci calculation provided in the original snippet...

Playground.

Answer №1

Upon further investigation, I realize the importance of attentive reading. In a PR submitted by Anders, it is explicitly mentioned that there are still limitations:

The compiler now performs type resolution in a loop that consumes no extra call stack. We permit evaluation of such types to loop 1000 times before we consider the type non-terminating and issue an error.

This realization leads me to believe that our luck may not be on our side, making computations involving arbitrary complexity seemingly impossible. However, my past experiences with C++ have equipped me with a valuable trick.

Laying out the problem: we are iterating a particular function multiple times until it "completes". Let's simplify this concept using a function that appends a '1' to a string.

type Test0<X> = X extends string ? `${X}1` : never

We can perform two iterations as follows:

type Test1<X> = Test0<Test0<X>>
// Test1<''> = '11'

Subsequent iterations will exponentially increase the number of iterations:

type Test2<X> = Test1<Test1<X>>
// Test2<''> = '1111'

We introduce an additional parameter to abstract over the doubling iterations: (unary) number of doublings.

type TestN<X, L extends string> = L extends `1${infer L}` ? TestN<TestN<X, L>, L> : Test0<X>
// TestN<'', '1111111111111111'> = '<one 65536 times>'

By employing this technique, we can achieve 2999 iterations of Test0 with TestN<'', 'one 999 times'>. This should allow us to reach our desired output before hitting any instantiation limits. Despite the potential for so many iterations, not all will be utilized for every function. To ensure early termination, we designate a "done" value.

type Ten = '111111111'
type Test0<X> =
    // exit condition
    X extends Ten ? {done: X} :
    // body
    X extends string ? `${X}1` : never

type TestN<X, L extends string> =
    L extends `1${infer L}`
        // const m = testN(x, l);
        ? TestN<X, L> extends infer M
            // if done, exit early, otherwise continue
            ? M extends {done: any} ? M : TestN<M, L>
            : never
        : Test0<X>
// this is very fast compared to possible 2^32 iterations
TestN<'', '11111111111111111111111111111111'>["done"] = Ten

Addressing performance concerns, increasing the recursive calls from 2 to K results in at most K999 iterations of Test0. Even for small values like K = 2, the function can iteratively compute without constraints.

During the exit phase, the function takes the "passthrough" route at most once per level (

O(K)</code), multiplied by the iteration depth <code>O(L)
. To minimize unnecessary computations, the branching level and depth should be optimized. Choosing a value of 2 provides a good balance.

With this strategy, I was pleasantly surprised to find that I could intricately calculate F20, without having focused on optimization techniques. However, there is another undisclosed limitation (TS code, instantiateTypeWithAlias) that encourages obscure coding practices.

instantiationCount >= 5000000

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

How can I incorporate the LIKE operator in a query when dealing with PostgreSQL's String array type using TypeORM?

My database backend is PostgreSQL and I have a TypeORM object simplified as follows: @Entity() @Index(['name'], {unique: true}) export class Foo extends BaseEntity { @PrimaryGeneratedColumn('uuid') id: string; @Column() name: st ...

"encountered net::ERR_NAME_NOT_RESOLVED error when trying to upload image to s3 storage

I am currently developing an application using Angular. I have been attempting to upload a picture to my S3 bucket, but each time I try, I encounter this error in the console. https://i.stack.imgur.com/qn3AD.png Below is the code snippet from my upload.s ...

Find out if all attributes of the object are identical

I am trying to create the boolean variable hasMultipleCoverageLines in order to determine whether there are multiple unique values for coverageLineName within the coverageLines items. Is there a more efficient way to write this logic without explicitly c ...

How can I activate TypeScript interface IntelliSense for React projects in VSCode?

Did you know that there are TypeScript interfaces designed for DOM, ES5, and other JavaScript modules? I am curious if it is feasible to have intellisense similar to the one provided by TypeScript Playground for various interfaces in a React project. ...

Error encountered: "The requested resource does not have the 'Access-Control-Allow-Origin' header in Angular 6 and Nodejs."

I have encountered an issue with my Angular 6 app and Node.js. When I submit the form, I am receiving the following error: Failed to load http://localhost:3000/contact/send: Response to preflight request doesn't pass access control check: No 'Ac ...

Using Angular: Binding Angular variables to HTML for display

I have a component with a ts file that contains HTML content as a variable. Let's say para1= <a href="www.google.com">sitename</a> more content I want to bind this paragraph in HTML as if it were actual HTML tags. sitename What is the ...

Interactive website built on Angular 16 offering advanced search and result display functionalities, along with options to edit and update data

Seeking guidance from experienced Angular developers as I am relatively new to the framework. Any tips or advice would be greatly appreciated. Project Overview: Front-end development using Angular, minimal focus on Back-end (C#) for now. https://i.sstati ...

Using Type TTextKey to access TOption is not allowed

I'm struggling to understand why there is a complaint about return option[optionTextKey] with the error message: Type TTextKey cannot be used to index type TOption Here's the code snippet causing the issue: type Props< TTextKey, TOpti ...

Transform array sequences into their own unique sequences

Reorder Array of List to Fit My Custom Order Current Output: [ { "key": "DG Power Output", "value": "6.00", "unit": "kWh", }, { "key": "DG Run Time", "value": "5999999952", "unit": "minutes", }, { "key": "Fuel Level (Before)", "value": "8.00" ...

Best practice for importing ts files from an npm package

When faced with the need to divide a ts project into multiple repositories/packages for creating microservices, the challenge arises in combining these packages efficiently. Some packages are required in one microservice, others in another, and some in all ...

Utilizing React TypeScript within Visual Studio

Utilizing the most recent editions of Visual Studio and Visual Studio Code, I have initiated a project using create-react-app with TypeScript react scripts version. My objective is to host my project within a .NET Core web application that I've estab ...

My goal is to intentionally trigger an eslint error when importing a file from index.ts

Is there a way to enforce importing components from index.ts within the src/components directory using eslint rules or plugins? // index.ts (src/components/Forms) export { Input } from './Input'; export { CheckBox } from './CheckBox'; ...

Enhance the variety of types for an external module in TypeScript

I am in the process of migrating an existing codebase from a JavaScript/React/JSX setup to TypeScript. My plan is to tackle this task file by file, but I have a question regarding the best approach to make the TypeScript compiler work seamlessly with the e ...

The unknown number of arguments in a Typescript generic type

I'm currently working on developing a function that utilizes a generic type to take in a function, an array of arguments for the function, and then apply them accordingly. However, I am facing an issue with TypeScript not correctly interpreting the ar ...

Alert: Attempting to access an undefined value in an indexed type

I would like to find a way in Typescript to create a hashmap with indexable types that includes a warning when the value could potentially be undefined during a lookup. Is there a solution for this issue? interface HashMap { [index: number]: string; } ...

Having trouble getting Typescript code to function properly when using commonjs style require statements

I am completely new to Typescript, Node.js, and Express. Following the instructions outlined in this tutorial (https://www.digitalocean.com/community/tutorials/setting-up-a-node-project-with-typescript), I set up my project exactly as described there. The ...

Instead of returning an object, the underscore groupBy function now returns an array

Currently, I am attempting to utilize underscore to create an array of entities that are grouped by their respective locations. The current format of the array consists of pairs in this structure { location: Location, data: T}[]. However, I aim to rearran ...

The return type of Array.find is accurate, however, it contains an error

Trying to find a way to indicate the expected return type of Array.find() in TypeScript, I encountered an incompatibility warning. View code in playground class A { "type"="A" t: string; #a = 0 constructor(t: string) { ...

Tips for preventing a wrapped union type from collapsing

It seems like there is an issue with Typescript collapsing a wrapped union type when it knows the initial value, which is not what I want. I'm uncertain if this is a bug or intended behavior, so I'm curious if there's a way to work around it ...

Could someone teach me how to implement icon rotation in Vue.js using Vuetify?

I am currently working on adding a feature where the icon rotates when the button is clicked, moving from down to up and vice versa in a spinning motion. Here is the code I have so far: <template> <v-btn v-on:click="isShow = !isShow" ...