The popular and accepted answer to this question is actually misleading, because the question itself is confusing. The OP does not make the distinction between tailrec
and TCO
, and the answer does not address this.
The key point is that the requirements for tailrec
are more strict than the requirements for TCO
.
The tailrec
annotation requires that tail calls are made to the same function, whereas TCO
can be used on tail calls to any function.
The compiler could use TCO
on fact
because there is a call in the tail position. Specifically, it could turn the call to fact
into a jump to fact
by adjusting the stack appropriately. It does not matter that this version of fact
is not the same as the function making the call.
So the accepted answer correctly explains why a non-final function cannot be tailrec
because you cannot guarantee that the tail calls are to the same function and not to an overloaded version of the function. But it incorrectly implies that it is not safe to use TCO
on this method, when in fact this would be perfectly safe and a good optimisation.
[ Note that, as explained by Jon Harrop, you cannot implement TCO
on the JVM, but that is a restriction of the compiler, not the language, and is unrelated to tailrec
]
And for reference, here is how you can avoid the problem without making the method final
:
class C {
def fact(n: Int): Int = {
@tailrec
def loop(n: Int, result: Int): Int =
if (n == 0) {
result
} else {
loop(n - 1, n * result)
}
loop(n, 1)
}
}
This works because loop
is a concrete function rather than a method and cannot be overridden. This version also has the advantage of removing the spurious result
parameter to fact
.
This is the pattern I use for all recursive algorithms.