I am starting to learn ocaml, and am really appreciating the power of recursion in the language. However, one thing that I am worried about is stack overflows.
If ocaml uses the stack for function calls, won't it eventually overflow the stack? For example, if I have the following function:
let rec sum x =
if x > 1 then f(x - 1) + x
else x;;
it must eventually cause a stack-overflow. If I was to do the equivalent thing in c++ (using recursion), I know that it would overflow.
So my question is, is there built in safeguards to prevent functional languages from overflowing the stack? If not, are they not less useful like this, since the above summing algorithm, written in a procedural style with a for loop, could handle any number (dis-regarding integer overflow)?
It's certainly easy for novices to write deep recursions that blow the stack. Objective Caml is unusual in that the library
List
functions are not stack-safe for long lists. Applications like Unison have actually replaced the Caml standardList
library with a stack-safe version. Most other implementations do a better job with the stack. (Disclaimer: my information describes Objective Caml 3.08; the current version, 3.11, may be better.)Standard ML of New Jersey is unusual in that it doesn't use a stack, so your deep recursions keep going until you run out of heap. It's described in Andrew Appel's excellent book Compiling with Continuations.
I don't think there's a serious problem here; it's more a "point of awareness" that if you are going to be writing a lot of recursive code, which you're more likely to do in a functional language, you have to be aware of non-tail calls and of stack size as compared to the size of the data you'll be processing.
This is tricky -- in principle yes, but the compilers and runtimes for functional languages account for the increased degree of recursion in functional languages. The most basic is that most functional language runtimes request a much larger stack than normal iterative programs would use. But in addition to that a functional language compiler is much more able to transform recursive code into a non-recursive due to the much stricter constraints of the language.
All (decent implementations of;-) functional languages optimize tail recursion, but that's not what you're doing here, since the recursive call is not the LAST operation (it needs to be followed by the addition).
So, one soon learns to use an auxiliary function that IS tail recursive (and takes the current total being accumulated as an argument) so the optimizer can do its job, i.e., net of possible O'Caml syntax in which I'm rusty:
Here, the sum happens as an ARGUMENT to the recursive call, i.e., BEFORE the recursion itself, and so tail optimization can kick in (because the recursion IS the last thing that needs to happen!).
Some functional languages such as Scheme specify that tail recursion must be optimized to be equivalent to iteration; hence, a tail-recursive function in Scheme will never result in a stack overflow, no matter how many times it recurses (presuming, of course, that it doesn't also recurse or engage in mutual recursion in other places besides the end).
Most other functional languages don't require tail recursion to be implemented efficiently; some choose to do so, others do not, but it's relatively easy to implement, so I would expect that most implementations do so.
Functional languages typically have a MUCH larger stack. For example, I've written a function specifically to test stack limits in OCaml, and it got to over 10,000 calls before it barfed. However, your point is valid. Stack-overflows are still something you need to watch out for in functional languages.
One of the strategies that functional languages use to mitigate their reliance on recursion is the use of tail-call optimization. If the call to the next recursion of the current function is the last statement in the function, the current call can be discarded from the stack and the new call instantiated in its place. The assembly instructions that are generated will be basically the same as the ones for while-loops in imperative style.
Your function is not tail-call optimizable because the recursion is not the last step. It needs to return first and then it can add x to the result. Usually this is easy to get around, you just create a helper function that passes an accumulator along with the other parameters