Tail Recursion
Interviewer task: get the interviewee to write factorial tail-recursively
Interviewer says: use recursion to implement factorial :
Interviewee nearly jumps with glee:
function factorial (n){
return n ? n * factorial(n - 1) : 1
}
Tail Recursion
Interviewer: What's the problem with this implementation? (Give interviewee time to respond).
Interviewer: This function calls itself n times and therefore needs n stack frames. JavaScript has a limit on the stack size.
factorial(10000) -->
RangeError: maximum call stack size exceeded
Text
fact(4)
#=> 4 * fact(3)
#=> 4 * ( 3 * fact(2) )
#=> 4 * ( 3 * ( 2 * fact(1) ) )
#=> 4 * ( 3 * ( 2 * 1 ) )
#=> 4 * ( 3 * 2 )
#=> 4 * 6
#=> 24
This implementation of factorial invokes * last, not factorial.
Which is why we need n stack frames
factorial(n){
return n ? n * factorial(n - 1) : 1
}
Tail-Recursion --> there is nothing to do after the function returns except return its value
A compiler implements tail call optimization if, when it recognizes a tail-recursive function, it turns the recursive call into a while loop, thereby reusing the current stack frame
no more worrying about space inefficiency
function factorial(n) {
function recur(n, acc) {
if (n == 0) {
return acc;
} else {
return recur(n-1, n*acc);
}
}
return recur(n, 1);
}
factorial(5) → recur(5,1)
recur(5,1) → recur(4,5)
recur(4,5) → recur(3,20)
recur(3,20) → recur(2,60)
recur(2,60) → recur(1,120)
recur(1,120) → recur(0,120)
recur(0,120) → 120
1 - a loop that iteratively invokes thunk-returning functions
2 - a tool used to implement tail-recursive function calls in stack-oriented programming languages
function trampoline(f) {
while (f && f instanceof Function) {
f = f.apply(f.context, f.args);
}
return f;
}