Is there a way to enable Tail Recursion Optimization in TypeScript?

const isPositive = (n: number) => n > 0;

function fitsIn(dividend: number,
                divisor: number,
                count: number,
                accum: number): number {
  if (accum + divisor > dividend) {
    return count;
  }
  return fitsIn(dividend, divisor, count + 1, accum + divisor);
}

function divide(dividend: number, divisor: number): number {
  let timesFits = fitsIn(Math.abs(dividend), Math.abs(divisor), 0, 0);
  return isPositive(dividend) === isPositive(divisor) ? timesFits : -timesFits;
}

console.log(divide(10, 3));
// 3

console.log(divide(-2147483648, -1));
// RangeError: Maximum call stack size exceeded

console.log(divide(10000, 1));
// RangeError: Maximum call stack size exceeded

I attempted to execute this code using TypeScript 4.6.2 in Strict Mode and encountered a stack overflow error. The issue seems to be related to the recursive nature of the function combined with accumulation within each recursive call. Is there a way to optimize this code for tail recursion? What modifications are needed to achieve tail recursion optimization?

Answer №1

When it comes to TypeScript, optimizing tail-called recursive functions is not on the cards. For more information, refer to microsoft/TypeScript#32743.

In JavaScript, 'tail call optimization' typically involves converting a tail-recursive function into an iterative version. This differs slightly from 'tail call elimination,' where recursion is maintained but the current stack frame is rewritten instead of creating a new one. While both methods appear similar in terms of stack growth, optimization generally operates at a higher level than elimination.


If you are considering suggesting that TypeScript should incorporate 'tail call optimization' for performance gains, it goes against TypeScript's design principles. The compiler aims to emit your code as it is written, without optimizing or altering it. Therefore, automatic optimization is not within TypeScript's scope.


Alternatively, you might be referring to 'proper tail call elimination' introduced in ECMAScript 2015 (ES6). Although this feature was intended to prevent adding to the call stack with tail calls, its adoption has been limited, primarily observed in Safari-based browsers. Runtime engine developers encountered challenges like potential performance issues and user confusion when implementing this feature, leading many engines to take a cautious approach. As of now, there is no concrete proposal for explicit syntax to enable such eliminations.

While it is technically feasible for TypeScript to downlevel proper tail call elimination into a form of tail call optimization, similar limitations would likely arise, prompting the team to refrain from pursuing it.


In conclusion, automatic tail call elimination seems to be inactive in the current landscape, with TypeScript showing no intentions to revive this feature.

Answer №2

Check this out - it might be exactly what you're looking for:

function Tailcall <T> = { head: T, tail: () => Tailcall<T>, done: boolean } ;

const Tailcall = 
(done: boolean) => 
<T,> (head: T) => 
(tail: () => Tailcall<T>)
: Tailcall<T> => 
    
    ({ head: head, tail: tail, done: done }) as Tailcall <T> ;

Tailcall.done = 
<T,> (head: T)
: Tailcall<T> => 
    
    Tailcall (true) (head) 
        (() => { throw new Error("not implemented"); }) ;

Tailcall.call = 
<T,> (tail: () => Tailcall<T>)
: Tailcall<T> => 
    
    Tailcall (false) (null as any) (tail) ;

Tailcall.RUN = 
<T,> (self: Tailcall<T>)
: T => 
{
    let RUNNING = self;
    while (!RUNNING.done) 
    { RUNNING = RUNNING.tail (); }
    return RUNNING.head ;
} ;


const rem = 
(n: number, r: number)
: Tailcall<number> =>
    
    (n < r) ? Tailcall.done(n) 
    : Tailcall.call(() => rem(n-r, r)) ;


const pipeline = <T,> (x: T) => <R,> (f: (x: T) => R) => pipeline((f) (x)) ;

pipeline (rem(10000002,2))
    (Tailcall.RUN)
    (console.log); // 0, no stack overflow here

Alternatively, you can also explore this classic-style implementation:

class TailCall
<T> 
{
    constructor
    (
        public readonly isComplete: boolean ,
        public readonly result: T ,
        public readonly nextCall: () => TailCall<T> ,
    ) {} ;
    
    static done
    <T>(value: T)
    : TailCall<T> 
    {
        return new TailCall(true, value, () => { throw new Error("not implemented"); }) ;
    } ;
    
    static call
    <T>(nextCall: () => TailCall<T>)
    : TailCall<T> 
    {
        return new TailCall(false, null as any, nextCall);
    } ;
    
    invoke(): T 
    {
        let tailcall: TailCall<T> = this ;
        while (!tailcall.isComplete) 
        { tailcall = tailcall.nextCall() ; } ;
        return tailcall.result ;
    } ;
} ;

const rem = 
(n: number, r: number)
: TailCall<number> =>
    
    (n < r) ? TailCall.done(n) 
    : TailCall.call(() => rem(n-r, r)) ;

console.log(rem(10000001,2).invoke()); // output: 1

These implementations are based on tail recursion concepts discussed in this Java blog.

The code is sourced from pure.ts.

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

"Discover the power of D3.JS with three dazzling charts on a single page, plus a host of additional

As a student, I struggle with English and it makes me feel sorry and anxious... Despite that, I want to create three scatter plot charts on the same page using a single data file (csv). The dataset includes columns for Name, Height, Weight, and Grade (ra ...

Steps for choosing the nth HTML row with jQuery

I'm facing a situation where I need to be able to select the nth row of an HTML table based solely on the id of the selected row. You can see exactly what I mean by checking out this JSFiddle Demo <table class="mytable1"> <tr><td i ...

Encountered an issue with the Mongoose Schema method: The model method is not recognized as a

Here are two Mongoose model schemas that I am working with. The LabReport model includes an array that references the SoilLab model. Within the SoilLab model, there is a static method that was initially used to choose which fields to display when retrievin ...

React: a versatile and type-specific onChange() function

After adding as HTMLInputElement, the error message of Property 'checked' does not exist on type 'EventTarget & (HTMLInputElement | HTMLTextAreaElement)' is resolved. However, I find it interesting that TypeScript doesn't autom ...

How does Backbone.js tackle error handling when receiving messages from Rails?

Can anyone using backbone.js lend a hand? How can error messages from a rails app be encoded when integrating with backbone.js? For instance, how should flash messages like "record not found" be handled? While errors are usually defined on the client sid ...

Successfully resolving the API without encountering any response errors, even after sending a response

Once the data is successfully saved in the database and the image upload is completed, I am attempting to send res.json. However, I keep encountering the error message API resolved without sending a response for /api/auth/registeration, this may result in ...

the process of accessing information from a service in an Angular Typescript file

After making a POST request using Angular's HTTP client, the response data can be accessed within the service. However, is there a way to access this data in the app.component.ts file? I am able to retrieve the response data within the service, but I ...

Why did the homepage load faster than the app.component.ts file?

I am facing an issue where the homepage is loading before app.component.ts, causing problems with certain providers not working properly due to import processes not being fully completed. Despite trying to lazy load the homepage, the console.log still sho ...

Encountering a DOM exception with React 16.6 due to lazy loading/Susp

I am currently working on implementing dynamic import in my React application. Most of the React examples I have seen involve rendering the application to a specific tag and replacing its content, like this: ReactDOM.render(<App />, document.getEle ...

Displaying time text in input element due to browser bug

I am faced with a perplexing puzzle that has left me scratching my head. Here are two seemingly identical pieces of javascript code, but one behaves unexpectedly (take note of the Console.Log): Updates the UI once, then abruptly stops updating: http://js ...

WordPress: Issue with Dropdown onchange Event Not Triggering

I'm having trouble getting an event to fire when the dropdown value changes. Can anyone help me figure out where I might be going wrong? JavaScript code jQuery(document).ready(function($){ jQuery('#_ccounts select').on('change&apo ...

Assign custom keys to request object parameters before reaching the controller in the map

I have a Loopback 4 application where the request object's property keys are in snake_case and they correspond to our database column names which are in StudlyCase. What I want is to change the application property names to camelCase. This means that ...

`req.user` seems to be unresolved, but it is actually defined

Currently, I am working on developing an Express.js application that utilizes Passport.js for authentication in an administration panel. The program is functioning correctly at the moment, with my app.js initializing passport and setting up sessions proper ...

How to automatically set the radio button as "checked" with Javascript upon loading the page

With the dark mode switch javascript from Bootstrap 5 (https://getbootstrap.com/docs/5.3/customize/color-modes/#javascript), I am attempting to ensure that a radio button is set to "checked" when the page loads, as well as when the color mode is changed. ...

Utilize the 'response.download' method to retrieve unique data not typically expected for a given request

Each time I try to download a file, the response I get is different. The file is generated correctly every time: user,first_name,last_name,active,completed_training 4,Foo,Bas,YES,YES 5,Ble,Loco,NO,NO 9,gui2,md,NO,NO 3137,foo,baz,NO,NO However, the respons ...

Can we selectively execute certain tests in Cypress using support/index.js?

I need to selectively run certain tests from a pool of 50 test files located in the integration folder. Specifically, I only want 10 of them to execute. In an attempt to achieve this, I am trying to configure the selection process within the support/index. ...

Identify the page search function to reveal hidden content in a collapsible section

Our team has implemented an expandable box feature on our wiki (in Confluence) to condense information, using the standard display:none/block method. However, I am looking for a way to make this work seamlessly with the browser's find functionality. S ...

Exploring the Possibilities of Greensock within Meteor Templates

Attempting to add animation to a div within a Meteor template using TweenLite with the gsop package installed. The template html code: <template name="mainInit"> <div id="teaContainer"> <h1>Tea</h1> </div> </template& ...

Different ways to display a static content list using Vue's named slots

I'm struggling to make the following setup work: My parent template <comp> <a href="#" slot="links">link 1</a> <a href="#" slot="links">link 2</a> </comp> and in my comp ...

Utilizing the @keypress event handler in VueJS

I am attempting to incorporate the onkeypress event within a Vue component. My goal is to allow only numbers on keypress while disallowing all other key codes. Here is what I have tried: Implementing the onkeypress event works perfectly! <input type=&q ...