什么是Scala注释以确保优化尾递归函数?


98

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

Answers:


119

从“ 尾巴,@ 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 = {
                    ^

44

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

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


8
我想这有点像overrideJava中的注释-代码可以在没有注释的情况下工作,但是如果您将其放在此处,它会告诉您是否出错。
佐尔坦

23

注释为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是自动私有的,因为它是在另一种方法的范围内定义的。


2
扩展您在上面所说的内容,Scala只能优化单个方法的尾部调用。相互递归调用不会被优化。
Rich Dougherty 2012年

我讨厌被挑剔,但是在您的示例中(无),您应该返回正确的列表长度函数的计数,否则在递归完成时,您总是会得到0作为返回值。
Lucian Enache
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.