In a recent StackOverflow answer, I gave the following recursive code:
def retry[T](n: Int)(fn: => T): T = {
try {
fn
} catch {
case e if n > 1 =>
retry(n - 1)(fn)
}
}
If I add the @tailrec
annotation, I get:
Could not optimize @tailrec annotated method retry: it contains a recursive call not in tail position.
I was able to hack a tail-recursive alternative, but I still wonder why this didn't optimize. Why not?
I don't think the list of implemented transformations for putting code in tail-recursive form include traversing exception-handling blocks. These are particularly tricky, even though you can come up with examples (as you have) of where it ought to be legal. (There are many cases that look legal which are not (e.g. if there is a
finally
block), or require considerably more complex winding/unwinding rules.)I found this solution elsewhere on SO. Basically, use a
return
withfn
so that iffn
returns w/o exception, so will your function. Iffn
does throw, and the exception meets the conditionn > 1
, then the exception is ignored and the recursive call happens after the catch block.To be tail-recursion optimized, this has to be transformed into something like the following:
When it executes the
GOTO
to loop, it has to leave to scope of thecatch
block. But in the original recursive version, the execution of the recursive call is still within thecatch
block. If the language allows that this could ever potentially change the meaning of the code, then this wouldn't be a valid optimization.EDIT: From discussion with Rex Kerr in the comments, this is a behaviour-preserving transformation in Scala (but only when there is no
finally
). So apparently it's just that the Scala compiler doesn't yet recognise that the last call of acatch
block where there is nofinally
is in a tail-call position.