How TCO works

Let's compare two stack frames, one without TCO and the other with TCO. Let's have a look at the following code first:

function a(x)
{
y = x + 2;
return b(y);
}

function b(y)
{
z = y + 3;
return z;
}

console.log(a(1)); // 6

Once allocated to memory, without using TCO, the three stack frames from of the previous code would look like the following diagram:

A typical last-in, first-out (LIFO) call stack

Once value 6 is assigned to variable z, the stack frame is ready to be popped. In this case, stack frame 2 is kept entirely in memory only to keep the address of console.log(). This is where TCO can make a difference. If, before calling b(), stack frame 2 were to be popped from the stack while keeping the original caller's return address intact, only one function would get stacked at any given moment at runtime and stack space would be reduced.

The entire stack would only count two stack frames no matter how many times functions would get tail-called. A tail-called optimized stack would therefore look like this:

A tail-call optimized call stack

Some have stated that implementing TCO would be a bad idea in certain JavaScript implementations, as doing so would disrupt the actual execution flow of an application, make debugging harder and break telemetry software in general. This might be the case for certain JavaScript implementations, but it is certainly not true in the absolute sense. Technically speaking, implementing TCO might prove to be difficult due to technical debt in certain JavaScript implementations, but it is certainly not necessary to require a syntactic flag for something that should be implicit in any language, especially when using a strict mode flag.

This being said, not all browsers and JavaScript projects have implemented this ES6 feature yet, but it is a question of time before they will have to do it and developers should be ready for this major change. Indeed, this change from the structural to the functional paradigm will make it possible to make very efficient loops using functions rather than well-known loop structures. The main advantages of programming according to these new principles will be:

  • Greater efficiency by consuming less memory and taking less time to complete large-sized loops
  • Less cyclomatic complexity and simplified critical paths
  • A reduced number of lines of code and less cognitive burden for the developer
  • Encapsulated and well-organized code
  • Better tested code in general

As of the time of writing, only Safari 11, iOS 11, Kinoma XS6 and Duktape 2.2 fully support tail-call optimization.

Let's take the two following code examples (chap8_js_performance_1.html and chap8_js_performance_2.html) in order to compare the performance of a traditional for loop with a tail-call optimized function. Here is the first example:

function myJS()
{
"use strict";

function incrementArrayBy2(myArray, len = 1, index = 0)
{
myArray[index] = index;
myArray[index] += 2;
return (index === len - 1) ? myArray : incrementArrayBy2(myArray, len, index +
1); // tail call
}

let myArray = [];

for(let i = 0; i < 100000000; i++) {
myArray[i] = i;
myArray[i] += 2;
}

console.log(myArray);
}

Here is the second:

function myJS()
{
"use strict";

function incrementArrayBy2(myArray, len = 1, index = 0)
{
myArray[index] = index;
myArray[index] += 2;
return (index === len - 1) ? myArray :
incrementArrayBy2(myArray, len, index +
1); // tail call
}

let myArray = [];

myArray = incrementArrayBy2(myArray, 100000000);

console.log(myArray);
}

If we benchmark these two scripts, we will notice that there is not that much of a difference between the two, except that the one that uses tail-calls can be more easily unit tested, has a very simple critical path and could easily be memoized as it is still referentially transparent even if not pure for obvious reasons.

Here are the results for the first script:

The results when using a structural 'for' loop

And, the results of the second script are:

The results when using stacked functions that are tail-call optimized

Now, let's try to have a better grasp of this ES6 feature through a few code examples that will allow us to better recognize the different ways in which tail-calls can be used.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset