Properly Biting Javascript's Tail
All code is editable with live results.
Recursion and TCO post-es2015
One—or maybe the most exiting feature of es2015—is tail call optimization or TCO.
Recursion can be problematic due to limitations on the call-stack size. Each time you call a function from within another function, the JavaScript engine remembers where you did the call. It does that for various reasons One way I like to think of it is the JS engine needs to remember the context your function runs in, especially local variables.
But since you have a limit to the amount of memory available on your machine, there is a limit to the call stack size and your theoretically valid JS code can crash because of this limit.
Let’s see an example with a function doing a recursive addition:
Now let’s make a TCO version of it. Our goal here is to give hints to the JS engine to detect that it can optimize our function into a TCO one. For the JS engine this optimization is seen as: oh cool I don’t need to remember the previous function call environment, so I will just reuse it for this new function call. JS engines can implement this forgetting part differently, what’s important is the call stack does not grow.
You need a recent browser for this to work, at the time I write this Safari and Canary support it
There are two simple steps:
- The first step is to use strict mode. Easy enough.
- The second one is to make sure that in our return statements, no
extra work is happening. We only want to return a function with
some parameters. It means we went to something like
n + addMe(n-1)
—note the extran +
computation here— to__add(n, 1)
and__add(n-1, acc + n)
.
I am now using an accumulator that I pass as a parameter to store my results. Another way to see it is I am using the arguments of my function calls to store the results instead of the extra addition after the function returns.
Yes I know what you are thinking, the function looks a little more complicated now. But it is now possible to use recursion everywhere in Javascript. It opens the possibility of an even more functional approach to Javascript and with this—and I am a strong believer of it—robustness comes.
Recursion and TCO pre-es2015
If your JS environment does not support TCO you still have some options available
You can unroll the recursion call and just use a regular loop.
You can catch the stack error.
But this is really ugly to have your code depend on errors thrown.
You can use trampolining
With trampolining each partial result represents a function call. This function call can either returns another partial result or return the final result.