109

I think there is @tailrec annotation to ensure the compiler will optimize a tail recursive function. Do you just put it in front of the declaration? Does it also work if Scala is used in scripting mode (for instance using :load <file> under REPL)?

huynhjl
  • 41,520
  • 14
  • 105
  • 158

3 Answers3

130

From the "Tail calls, @tailrec and trampolines" blog post:

  • In Scala 2.8, you will also be able to use the new @tailrec annotation to get information about which methods are optimised.
    This annotation lets you mark specific methods that you hope the compiler will optimise.
    You will then get a warning if they are not optimised by the compiler.
  • In Scala 2.7 or earlier, you will need to rely on manual testing, or inspection of the bytecode, to work out whether a method has been optimised.

Example:

you could add a @tailrec annotation so that you can be sure that your changes have worked.

import scala.annotation.tailrec

class Factorial2 {
  def factorial(n: Int): Int = {
    @tailrec def factorialAcc(acc: Int, n: Int): Int = {
      if (n <= 1) acc
      else factorialAcc(n * acc, n - 1)
    }
    factorialAcc(1, n)
  }
}

And it works from the REPL (example from the Scala REPL tips and tricks):

C:\Prog\Scala\tests>scala
Welcome to Scala version 2.8.0.RC5 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_18).
Type in expressions to have them evaluated.
Type :help for more information.

scala> import scala.annotation.tailrec
import scala.annotation.tailrec

scala> class Tails {
     | @tailrec def boom(x: Int): Int = {
     | if (x == 0) throw new Exception("boom!")
     | else boom(x-1)+ 1
     | }
     | @tailrec def bang(x: Int): Int = {
     | if (x == 0) throw new Exception("bang!")
     | else bang(x-1)
     | }
     | }
<console>:9: error: could not optimize @tailrec annotated method: it contains a recursive call not in tail position
       @tailrec def boom(x: Int): Int = {
                    ^
<console>:13: error: could not optimize @tailrec annotated method: it is neither private nor final so can be overridden
       @tailrec def bang(x: Int): Int = {
                    ^
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
48

The Scala compiler will automatically optimize any truly tail-recursive method. If you annotate a method that you believe is tail-recursive with the @tailrec annotation, then the compiler will warn you if the method is actually not tail-recursive. This makes the @tailrec annotation a good idea, both to ensure that a method is currently optimizable and that it remains optimizable as it is modified.

Note that Scala does not consider a method to be tail-recursive if it can be overridden. Thus the method must either be private, final, on an object (as opposed to a class or trait), or inside another method to be optimized.

Philippe
  • 9,582
  • 4
  • 39
  • 59
Dave Griffith
  • 20,435
  • 3
  • 55
  • 76
  • 8
    I suppose this is kind of like the `override` annotation in Java - the code works without it, but if you put it there it tells you if you made a mistake. – Zoltán Jan 05 '15 at 15:05
26

The annotation is scala.annotation.tailrec. It triggers a compiler error if the method can't be tail call optimized, which happens if:

  1. The recursive call is not in the tail position
  2. The method could be overriden
  3. The method is not final (special case of the preceding)

It is placed just before the def in a method definition. It works in the REPL.

Here we import the annotation, and try to mark a method as @tailrec.

scala> import annotation.tailrec
import annotation.tailrec

scala> @tailrec def length(as: List[_]): Int = as match {  
     |   case Nil => 0
     |   case head :: tail => 1 + length(tail)
     | }
<console>:7: error: could not optimize @tailrec annotated method: it contains a recursive call not in tail position
       @tailrec def length(as: List[_]): Int = as match { 
                    ^

Oops! The last invocation is 1.+(), not length()! Let's reformulate the method:

scala> def length(as: List[_]): Int = {                                
     |   @tailrec def length0(as: List[_], tally: Int = 0): Int = as match {
     |     case Nil          => tally                                       
     |     case head :: tail => length0(tail, tally + 1)                    
     |   }                                                                  
     |   length0(as)
     | }
length: (as: List[_])Int

Note that length0 is automatically private because it is defined in the scope of another method.

Ohashi
  • 397
  • 3
  • 14
retronym
  • 54,768
  • 12
  • 155
  • 168
  • 2
    Expanding on what you've said above, Scala can only optimise tail calls for a single method. Mutually recursive calls won't be optimised. – Rich Dougherty May 26 '12 at 04:19
  • I hate being the nit picky one, but in your example in the Nil case you should return tally for a correct list length function otherwise you will always get 0 as a return value when the recursion finishes. – Lucian Enache Jan 15 '17 at 22:50