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


98

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

回答:


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 のアノテーションのようなものだと思います。コードはアノテーションなしでも機能しますが、そこに配置すると、ミスを犯したかどうかがわかります。
ゾルタン、2015年

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は単一のメソッドの末尾呼び出しのみを最適化できます。相互に再帰的な呼び出しは最適化されません。
リッチドハティ

気の利いたものではないのが嫌ですが、Nilの場合の例では、正しいリストの長さ関数に対してタリーを返す必要があります。そうしないと、再帰が終了したときに戻り値として常に0が返されます。
Lucian Enache 2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.