Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why won't the Scala compiler apply tail call optimization unless a method is final?

Why won't the Scala compiler apply tail call optimization unless a method is final?

For example, this:

class C {     @tailrec def fact(n: Int, result: Int): Int =         if(n == 0)             result         else             fact(n - 1, n * result) } 

results in

error: could not optimize @tailrec annotated method: it is neither private nor final so can be overridden

What exactly would go wrong if the compiler applied TCO in a case such as this?

like image 202
Seth Tisue Avatar asked Jan 24 '11 18:01

Seth Tisue


People also ask

Does Scala have tail-call optimization?

Scala 2.7. x supports tail-call optimization for self-recursion (a function calling itself) of final methods and local functions.

What annotation ensures the Scala compiler will apply tail recursion optimizations?

I think there is @tailrec annotation to ensure the compiler will optimize a tail recursive function.

Does Scala have tail recursion?

Recursion is a method which breaks the problem into smaller subproblems and calls itself for each of the problems. That is, it simply means function calling itself. The tail recursive functions better than non tail recursive functions because tail-recursion can be optimized by compiler.

What languages support tail-call optimization?

This kind of function call is called a tail call, and languages like Haskell, Scala, and Scheme can avoid keeping around unnecessary stack frames in such calls. This is called tail call optimization (TCO) or tail call elimitation.


1 Answers

Consider the following interaction with the REPL. First we define a class with a factorial method:

scala> class C {          def fact(n: Int, result: Int): Int =            if(n == 0) result            else fact(n - 1, n * result)        } defined class C  scala> (new C).fact(5, 1) res11: Int = 120 

Now let's override it in a subclass to double the superclass's answer:

scala> class C2 extends C {          override def fact(n: Int, result: Int): Int = 2 * super.fact(n, result)        } defined class C2  scala> (new C).fact(5, 1) res12: Int = 120  scala> (new C2).fact(5, 1) 

What result do you expect for this last call? You might be expecting 240. But no:

scala> (new C2).fact(5, 1) res13: Int = 7680 

That's because when the superclass's method makes a recursive call, the recursive call goes through the subclass.

If overriding worked such that 240 was the right answer, then it would be safe for tail-call optimization to be performed in the superclass here. But that isn't how Scala (or Java) works.

Unless a method is marked final, it might not be calling itself when it makes a recursive call.

And that's why @tailrec doesn't work unless a method is final (or private).

UPDATE: I recommend reading the other two answers (John's and Rex's) as well.

like image 151
Seth Tisue Avatar answered Sep 23 '22 03:09

Seth Tisue