tags:

views:

485

answers:

4

The following would cause stack overflow for large 'n', and I can understand why.

def factorial(n)
  (n > 1) ? (return (n * factorial(n - 1))) : (return 1)
end

Why does the following cause overflow as well?

def factorial(n, k)
  (n > 1) ? (return factorial(n - 1, lambda {|v| return k.call(v * n)})) : (return k.call(1))
end
A: 

As in the first function, the recursive calls can end up being too many for the system to handle.

Beau Martínez
+3  A: 

In most languages, function calls go onto the call stack, which is really just the stack. Calling a function recursively keeps adding to the call stack. Eventually you fill up the stack, and you get a stack overflow. That's always a danger when running a recursive function where your recursion level is going to be deep.

Brad Barker
-1: cf. "The following would overflow for large 'n', and I can understand why"
Anton Tykhyy
+2  A: 

For the same reason the first one has a stack overflow... The callstack gets too large.

Matt Grande
+9  A: 

Your second algorithm creates a n-long chain of lambda procedures, each containing a reference to the previous one. I don't know exactly what Ruby does, but in a properly tail-recursive language the stack would not overflow in your second algorithm, because k.call in the lambda is also in tail position. If, as Brian's experiment suggests, Ruby doesn't have proper tail calls, the n-long chain of nested calls to the lambda will overflow the stack when the head of the chain is invoked, even though Ruby is smart enough to convert the tail-recursive factorial call into a loop (= tail-call optimisation).

Anton Tykhyy
The second version is still recursing on factorial. I thought Ruby didn't do TCO. But it doesn't blow the stack until it reaches the lambdas. I'm confused. (Everyone else is even more confused, or didn't read the question. I'm downvoting others appropriately.)
Brian Carper
"But it doesn't blow the stack until it reaches the lambdas." — er? I don't understand.
Anton Tykhyy
Given that the second example is still recursing on `factorial`, I expected it to overflow the stack before it ever reached the base case of `k.call(1)`. But in the second example code above, the calls to `factorial` don't overflow the stack (even for very large n), which is different behavior than the first example. The second example does reach that base case, and doesn't overflow until lots of `k.call` has taken place. Without TCO, I don't see how it's making it that far.
Brian Carper
Yep, I agree, some form of simple TCO must be there. Edited accordingly.
Anton Tykhyy
Clarify - 1. I thought I had a correct (if not the best) CPS style fn. if I'm wrong, what's the mistake? 2. IMHO, all programming languages would have some form of TCO. If that's the case like Anton said before, shouldn't that prevent the stack from overflowing? So Ruby does "and" doesn't have TCO? What am I overlooking?
spitfire
2. Ruby has incomplete TCO: it optimizes `factorial`, but doesn't understand how to optimize your lambda.
Anton Tykhyy