I have a recursive function and exhausting the call stack is an issue I run into sometimes. I know I can use streams, promises with setTimeout, but I would like to just write code that triggers tail call optimization. So far only Webkit seem to have implemented tail call optimization (TCO). Other than knowing the theory, is there any way do check if my code will trigger TCO, either with devtools or by examining the output of the Webkit compiler?
Yes, ES2015 offers tail call optimization in strict mode.
An easy way to tell if a recursive function is a tail recursive is if it returns a concrete value in the base case. Meaning that it doesn't return 1 or true or anything like that. It will more than likely return some variant of one of the method parameters.
Tail call optimization happens when the compiler transforms a call immediately followed by a ret into a single jmp . This transformation saves one instruction, and more importantly it eliminates the implicit push/pop from the stack done by call and ret .
Tail-call optimization is a part of the ES2015-ES6 specification. Supporting it isn't a NodeJS thing, it's something the V8 engine that NodeJS uses needs to support. Node 7.10 down to 6.5. 0 support this in strict mode only and with the flag “--harmony”.
I know it's not perfect, but I think it's the best option we have currently.
Consider this function:
"use strict";
function detectTCO() {
const outerStackLen = new Error().stack.length;
// name of the inner function mustn't be longer than the outer!
return (function inner() {
const innerStackLen = new Error().stack.length;
return innerStackLen <= outerStackLen;
}());
}
Error.stack
is not standard, but it has basic support in all modern browsers. It has different values in different browsers, nonetheless, when a longer named function tail-calls a shorter named one, the stack trace:
As of now, this returns true
for Safari, and false
for other browsers, which is the current TCO support.
"use strict";
function detectTCO() {
const outerStackLen = new Error().stack.length;
// name of the inner function mustn't be longer than the outer!
return (function inner() {
const innerStackLen = new Error().stack.length;
return innerStackLen <= outerStackLen;
}());
}
document.getElementById('result').innerText = detectTCO() ? 'Yes' : 'No';
#result {
color: blue;
font-weight: bold;
}
Is TCO available?
<div id="result"></div>
Could utilize .toString()
, RegExp.test()
to check for return
statement followed by underscore, or $ sign , a-z characters; followed by open parenthesis followed by any characters followed by closing parenthesis
function forEach(arr, callback, start) {
if (0 <= start && start < arr.length) {
callback(arr[start], start, arr);
return forEach(arr, callback, start + 1); // tail call
}
}
console.log(/return [_a-z$]+\(.*\)/i.test(forEach.toString()))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With