【问题标题】:What is the Scala annotation to ensure a tail recursive function is optimized?确保尾递归函数得到优化的 Scala 注释是什么?
【发布时间】:2011-03-08 01:08:52
【问题描述】:

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

【问题讨论】:

    标签: scala tail-call-optimization


    【解决方案1】:

    来自“Tail calls, @tailrec and trampolines”博文:

    • 在 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 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 = {
                        ^
    

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

      • 我想这有点像 Java 中的 override 注释 - 没有它,代码也可以工作,但如果你把它放在那里,它会告诉你是否犯了错误。
      【解决方案3】:

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

      【讨论】:

      • 扩展您上面所说的内容,Scala 只能优化单个方法的尾调用。不会优化相互递归调用。
      • 我讨厌挑剔,但在您的示例中,在 Nil 的情况下,您应该返回正确的列表长度函数的计数,否则当递归完成时,您将始终得到 0 作为返回值。
      猜你喜欢
      • 1970-01-01
      • 2011-01-25
      • 1970-01-01
      • 1970-01-01
      • 2010-10-20
      • 1970-01-01
      • 2014-09-22
      • 2018-03-08
      • 2020-06-13
      相关资源
      最近更新 更多