107

我认为有@tailrec注释可以确保编译器优化尾递归函数。你只是把它放在声明的前面吗?如果在脚本模式下使用 Scala(例如:load <file>在 REPL 下使用),它是否也有效?

4

3 回答 3

127

来自“尾部调用、@tailrec 和蹦床”博客文章:

  • 在 Scala 2.8 中,您还可以使用新的@tailrec注解来获取有关优化了哪些方法的信息。
    此注释允许您标记您希望编译器优化的特定方法。
    如果编译器未优化它们,您将收到警告。
  • 在 Scala 2.7 或更早版本中,您将需要依靠手动测试或检查字节码来确定方法是否已优化。

例子:

您可以添加一个@tailrec注释,以便您可以确保您的更改已经生效。

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)
  }
}

它适用于 REPL(来自Scala REPL 提示和技巧的示例):

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 = {
                    ^
于 2010-06-24T22:06:08.473 回答
48

Scala 编译器会自动优化任何真正的尾递归方法。如果您使用注释对您认为是尾递归的方法进行@tailrec注释,那么如果该方法实际上不是尾递归,编译器将警告您。这使得@tailrec注释成为一个好主意,既可以确保方法当前是可优化的,又可以在修改时保持可优化。

请注意,如果一个方法可以被覆盖,Scala 不会认为它是尾递归的。因此,该方法必须是私有的、最终的、在对象上(与类或特征相反)或在另一个要优化的方法内。

于 2010-06-24T22:05:32.460 回答
25

注释是scala.annotation.tailrec。如果方法不能进行尾调用优化,则会触发编译器错误,如果发生以下情况:

  1. 递归调用不在尾部位置
  2. 该方法可以被覆盖
  3. 该方法不是最终的(前面的特殊情况)

它位于def方法定义中的 之前。它在 REPL 中工作。

这里我们导入注解,并尝试将方法标记为@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 { 
                    ^

哎呀!最后一次调用是1.+(),不是length()!让我们重新制定方法:

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

请注意,它length0是自动私有的,因为它是在另一个方法的范围内定义的。

于 2010-06-24T22:06:10.747 回答