Tail Calls in JavaScript — Will there be a comeback?
By: Arnold Abraham
Tags: JavaScript, Web Development, Front End Development, Coding, Tech

Tail Calls in JavaScript — Will there be a comeback?

JavaScript got tail calls since Node.JS 6.17.1 but are they going to be supported again?

Pixabay via pexels (CC0)

A next-level that maybe won’t come to JavaScript, but other languages do support it.

In order to solve recurrence tasks, you have two options. Iteration or recursion. When you decide for iteration you use looping constructs, like the for loop, the forEach loop, and so on. If you pick the recursion, you are going to take a mechanism that allows functions to call themselves. The iterative part lies in the repeated calling of the function itself.

We can use a mathematical example to make this clear. The faculty is a mathematical function that defines all natural numbers from 1 to n for n. You can identify this construct by an exclamation mark after the number.

This mathematical operation results quickly in pretty large numbers. The faculty of 10 is already over 3 million. Mapping it into a mathematical term will cause this definition.

But it is also possible to declare mathematical recursion:

The main key aspect is to clarify that both representations are equal. It may occur that one fits better over the other, but this depends on the case and the instructions of the task you have. Using faculty inside your code will often lead to a pick of recursion. This makes it easier to comprehend that a calculation of 4! also includes the calculation of 3!.

Which one to pick? Recursive or iterative?

For the part of coding, we have to pick one, although both ways are principally the same, we can only implement one at a time. Both of them have advantages and disadvantages to be aware of.

Recursion

This is the way of how you achieve the faculty of a number in JavaScript using recursion.

Clear advantages are here the readability because we approach the problem with an already recursively formatted solution. Be careful when you pick recursion. Every new call of the same function needs random access memory. Some environments have limited RAM or if you work mainly with recursion, you can easily land in the stack overflow jail. This happens most of the time when you don’t define your break out checks properly.

Iteration

Here comes the way with the loops. Simply a for loop iterating as long as the factor is below the initial input value. For example input of 4 will iterate 3 times,resulting into 4*3*2*1 = 24;

We got no overhead here and no worries about stack overflow. What stands against this solution is the readability of the source code. The reason is not the loop construct, but the fact that a faculty is a recursive occasion and we have to port this recursion into an iteration construct when reading the code. This conversion costs time and slows the reading process.

Thinking about equality of both approaches will result in the thesis: If both approaches are equal, then every recursion can be converted into an iteration and vice versa. This means a compiler could compile our code into the part that codes/read the worst. We could then write code recursively for a recursive problem and let the compiler compile that code into an iteration statement. We don’t have to read anymore. We won't see the compiled code. It is only for the application to run.

Since JavaScript is using this principle for several other code parts like async & await or yield, which are mapped into a state machine, it couldn’t that difficult.

Convert with tail calls

We can achieve our theory when we use end-recursion, better known as Tail Call [1]. The name comes from the fact that the last call of this function is the recursive one. Inspecting closely the last line of X2_1_Recursion.js makes clear, that this function is not working with tail calls.

If you ignore the return statement, then the last action that is happening is the multiplication with n. The recursive call is already happened and evaluated. In order to transform this into a tail call, we have to implement a parameter in between. This parameter will then save an interim result and pass this value to the next call of the function. A prevailed name for that is “Accumulator” and we can shorten it with acc. Once we got all these steps, the last line will now look like this.

We are passing the new parameter acc into the function and have to change the whole recursive function of faculty.

Doing it like that and we can fulfill our requirement to write recursions that are converted into iteration ones after the compilation.

To explain this: We have to overwrite the parameter of the function in the stack and continue the code execution at the beginning of the function. This gives the impression the function calls itself recursively, but in reality, there is a jumping instruction of goto.

Support for Tail Calls in JavaScript

What I am explaining here sounds pretty comprehensible and easily implementable. The actual world looks quite different.

Coding in Node.JS and you will find the support during the different versions pretty quick. [2]

Until 6.4.0 was no support for tail calling at all

6.17.1 got support for them, also 7.5.0 & 7.10.1

Since 8.2.1 they kicked tail calls out again

This isn’t only Node.JS fault but more google chrome’s V8 Engine. They have a similar support history to the one of Node.JS. The primary reason it was kicked out was the lack of debugging support. Calling functions are in the source code but not in the stack anymore, leading to confusion since it looks like the function gets called only once. Making even more problems during error hunting and debugging at all.

One browser that supports tail calls is the safari browser! [6], [7]

Since 2016 there is an open discussion about that in the TC39 Proposal [3]. Some developers feel strongly about having tail-call optimization supported in JavaScript. There is currently an active proposal for tail calls that are explicitly opt-in by using a pre-defined keyword. The proposal states to call them Syntactic Tail Calls instead. They refer to tail-call optimization as Proper Tail Calls (PTC).

With a statement like return continue to declare a tail call, developers are able to choose to utilize this call stack optimization or not. In my opinion, this could be a good solution. It allows developers on both sides of the argument to get what they want. The developer has to be intentional to get what he wants, since utilizing tail calls would be explicit. Therefore, this solution would impute that the coder knows what he or she is doing.

Some could complain that they want it done the implicit way. Unfortunately, no matter what solution could come in the future. It won’t satisfy everyone. Although, I think this is a suitable compromise. Syntactic tail calls are still in a proposed state, and this topic came up also long ago and with many diverse opinions, it may still be years before we see this actually implemented in browsers.

But what is the most important thing if you look into my first link you will see that other languages are supporting this and you can go there back with the knowledge gained here! [1]

Conclusion

Even because of the last part and the long-term discussion, I think the responsible persons are using a tactic where everything is swept under the carpet. The discussion is open since 2016 and when I look into the plans of TC39 I don’t think this is a relevant topic anymore. What can be valuable is the way of writing code in an optimized way like mentioned here, knowing the difference between recursive and iterative constructs.

If you are working in the specific Node.JS versions of 6.17.1 up to 7.10, sticking to the ES version of ES2015/ES6, sticking only to safari or use another language you are going to save yourself a lot of time learning this way of coding. The V8 engine, which is used by Node.JS does no further pursuit of this kind of feature. [5]

About the uncertain future of TCO, see this answer in stack overflow: ES6 Tail Recursion Optimization.

Read On

Links

[1] Tail Call https://www.geeksforgeeks.org/tail-recursion/

[2] Tail Call optimisation NodeJS ES2015 https://github.com/nodejs/CTC/issues/3

[3] Diskussion TC39 https://github.com/tc39/proposal-ptc-syntax

[4] Stack Overflow post to TCO ES6 Tail Recursion Optimization

[5] Chrome Platform Status https://www.chromestatus.com/feature/5516876633341952

[6] Safari Support Tail calls https://medium.com/indigoag-eng/tail-call-optimization-in-the-wild-26a10e450c73#:~:text=Safari%20does!,that%20supports%20tail%2Dcall%20optimization.

[7] Safari Support Tail calls https://iryl.info/2020/09/19/improve-your-recursions-performance-with-tail-call-optimizations/