| > I don't see how it's possible to have recursion without a stack. Well, it depends what you mean by "without a stack", and it's also the case that for some recursive algorithms, you can make them stackless. Any recursive algorithm is isomorphic to a non-recursive algorithm and a separate stack (like a linked list), but that's still "with a stack". Many recursive algorithms are tail-call recursive, which means that you can reduce the algorithm to a fixed-space loop. For example, int fac'(int accum, int n){
if(n==0) return accum;
else return fac'(accum*n, n-1);
}
int fac(int n){return fac'(1,n);}
is equivalent to a loop int fac(int n){
int accum = 1;
while(n>0){
accum *= n;
n--;
}
return accum;
}
Compilers like GCC and Clang will make this optimization.Other (co)recursive algorithms are WHNF tail-call recursive, meaning that the algorithm can be expressed as a function that returns a partially evaluated value and another function to evaluate the next part of the value. This also requires constant space, but you won't find it in a whole lot of languages. It's popular in Haskell, for example: fibs a b = a : fibs b (a+b)
fib n = fibs 0 1 !! n
Even though "fibs" (the function that generates the list of Fibonacci numbers) is recursive, and, in fact, infinitely recursive, it requires constant space.You may also be interested in reading http://wwwhome.ewi.utwente.nl/~fokkinga/mmf91m.pdf , which explains a number of morphisms that might be leveraged to transform certain recursive functions into constant-space variants. |
Are you sure they make that optimization? How do you know? (I'm not familiar with either code base).
Are you absolutely sure that example is O(1)? It looks O(n) to me. I'm not experienced in Haskell, by the way: disclaimer.
Each call to 'fibs' constructs a list with its first argument and the recursive call. But for all of the recursions (aka, 2nd element and on) I think they'll have to build up deferred cons operations. I'm not sure about this at all. I sound suspicious, I know, but before you say anything, look here: http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-11.html...
don't they look suspiciously similiar? In that example, you'd have needed to store a function pointer and the argument'(s) type(s) for each call, on the stack, making space usage O(n). Each call pushes to the stack.
Does the Haskell example seem similar to you? I'm really uncertain.