2010-06-24 88 views

回答

94

從「Tail calls, @tailrec and trampolines」的博客文章:

  • 在斯卡拉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 = { 
        ^
32

Scala編譯器會自動優化任何真正的尾遞歸方法。如果您註釋了一種您認爲是使用@tailrec註釋進行尾遞歸的方法,那麼編譯器會警告您,如果該方法實際上不是尾遞歸的。這使得註釋成爲一個好主意,這既是爲了確保一種方法目前可以​​優化,並且它在修改後仍然可以優化。

請注意,如果可以覆蓋,Scala不會考慮一種方法是尾遞歸的。因此,該方法必須是私有的,最終的,在對象上(而不是類或特徵),或者在另一種方法中被優化。

+6

我想這有點像Java中的'override'註釋 - 代碼沒有它,但是如果你把它放在那裏,它會告訴你,如果你犯了一個錯誤。 – 2015-01-05 15:05:02

20

的註解是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只能優化單個方法的尾部調用。相互遞歸調用不會被優化。 – 2012-05-26 04:19:30

+0

我討厭成爲挑剔的人,但在你的例子中,在無情況下,你應該返回一個正確的列表長度函數,否則當遞歸完成時你總會得到0作爲返回值。 – 2017-01-15 22:50:14