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?
3 Answers
6
votes
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:
- with TCO: gets shorter, because the stack frame of the outer function gets replaced by the stack frame of the inner function.
- without TCO: gets longer, because the stack frame of the inner function gets appended.
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>
1
votes
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()))