web-dev-qa-db-ja.com

末尾再帰関数を最適化するためのScalaアノテーションとは何ですか?

@tailrec注釈は、コンパイラーが末尾再帰関数を最適化することを保証します。宣言の前に置いただけですか? Scalaがスクリプトモードで使用されている場合にも機能しますか(たとえば、:load <file> REPLの下)?

90
huynhjl

" 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ヒントとコツ )の例:

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 = {
                    ^
111
VonC

Scalaコンパイラは、真の末尾再帰メソッドを自動的に最適化します。末尾再帰であると思われるメソッドに@tailrecアノテーションを付けた場合、コンパイラは次の場合に警告しますメソッドは実際には末尾再帰ではありません。これにより、メソッドが現在最適化可能であり、変更されたときに最適化されたままであるようにするために、@tailrecアノテーションが良いアイデアになります。

Scalaは、メソッドをオーバーライドできる場合、メソッドを末尾再帰とは見なしません。したがって、メソッドは、クラスまたはトレイトではなく、オブジェクトのprivate、finalである必要があります) 、または最適化される別のメソッド内。

38
Dave Griffith

注釈は_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_は別のメソッドのスコープで定義されているため、自動的にプライベートであることに注意してください。

23
retronym