Let's simplify your example a bit:
function sum_tramp(x, y) {
if (y > 0)
return () => sum_tramp(x + 1, y - 1);
else
return x;
}
function sum_rec(x, y) {
if (y > 0)
return sum_rec(x + 1, y - 1);
else
return x;
}
x = sum_tramp(1, 1e6)
while (x instanceof Function)
x = x();
console.log(x)
x = sum_rec(1, 1e6) //Maximum call stack size exceeded
console.log(x)
Naive recursion (sum_rec
) requires all values to be computed before it can return anything, so to compute 6+4
, it has to compute 7+3
which requires 8+2
etc. The function doesn't exit until all subsequent calls are complete and its arguments are sitting on the stack until then.
A trampoline returns a computation, that is, instructions how to compute the value instead of the value itself. Since the computation is already known, the function exits immediately and doesn't fill up the stack. In the main program, we check if the returned value is a computation, and if yes, simply invoke it again. So, when computing 6+4
, we receive instructions how to compute 7+3
. We carry out these instructions, and get instructions how to compute 8+2
. Then, we carry them out... and so on, until we end up with a non-function return value.
As noted in the comments, some browsers already implement tail call optimizations, which makes trampolines unnecessary (but still worth knowing about).