Reputation: 113
The below simple function is tail recursive. However, it still throws java.lang.StackOverflowError
. Could anyone please help me understand why I get this?
I have tried tail recursive functions with lists that contain millions items, but never get StackOverflowError
. Or isn't this function tail recursive?
def recursion(i: Int): Int = {
if (i == 20000) i
else {
recursion(i + 1)
}
}
recursion(0)
Update: Adding @tailrec
does solve the problem, even if I increase the limit to 2147483647. Thanks @tiran for your answer
Upvotes: 1
Views: 1182
Reputation: 4662
In every language, with local differences, every time you invoke a function call, you create a new allocation on a memory zone called "stack" to store information about the function, such as parameters and return address. When the function returns you deallocate the corresponding stack block and return to the caller.
Suppose you have a function like:
def f(i:Integer) = {
if (i <= 0) 0
else f(i-1)
}
if you call f(3), you get:
f(3) -> calls -> f(2) -> calls -> f(1) -> calls -> f(0)
and your stack, when is at its maximum height, looks like:
stack(f(0))
stack(f(1))
stack(f(2))
stack(f(3))
Once the f(0) call returns, its block gets deallocated and so does, in sequence, the ones from f(1), f(2), f(3), and your stack deflates.
There is a limit to the maximum height that the stack of a program can have at any time. your function recurses 20000 times, which is more than such limit, so the JVM gets angry and throws that exception.
Adding @tailrec tells the computer to compile your function as tail recursive, i.e. to (more or less) transform it in a while loop that does not allocate stack space such as:
def recursion(i:Int) = {
while(i<20000) {
i++
}
i
}
Therefore you don't break the stack ceiling :)
EDIT: As pointed out by others, @tailrec doesn't tell the compiler to perform TCO, but asks the compiler to fail if TCO can't be performed on the method, thereby guaranteeing TCO on the method in case of compiling success.
Upvotes: 1
Reputation: 108169
The function is tail-recursive and in fact the compiler optimizes it as expected. Your example works just fine in my scala 2.11.2 REPL (I added a couple of zeros, just to prove the point better)
scala> def recursion(i: Int): Int = {
| if (i == 2000000) i
| else {
| recursion(i + 1)
| }
| }
recursion: (i: Int)Int
scala> recursion(0)
res0: Int = 2000000
About @tailrec
, it can't "solve" anything. @tailrec
is an annotation that triggers a compile-time error in case tail-call optimization (TCO) cannot be performed on the annotated function.
So, TCO is always performed whenever possible, regardless of the @tailrec
annotation.
Since your problem went away just recompiling, your IDE was probably in some dirty state and the edit simply triggered a clean compilation. The code was fine from the very begin.
Upvotes: 3