容易被忽略的類成員方法的尾遞歸優化限制

        所謂尾遞歸,就是方法的遞歸調用在方法體的最後。scala編譯器會把尾遞歸的字節碼優化成循環執行的形式,但有一個限制可能會被你們忽略.先看一個例子:java

class Approx {
  def isGoodEnough(guess: Double): Boolean =
    if (guess < 1) true
    else false

  def improve(guess: Double): Double = guess - 1

  @tailrec
  final def approximate(guess: Double): Double =
    if (isGoodEnough(guess)) guess
    else approximate(improve(guess))

  def approximateLoop(initialGuess: Double): Double = {
    var guess = initialGuess
    while (!isGoodEnough(guess))
      guess = improve(guess)
    guess
  }
}

object TailRecDemo {
  val app = new Approx()

  def main(args: Array[String]) = {
    println(System.getProperty("java.class.path"))
    recursive(1)
    iterate(1)
    recursive(10)
    iterate(10)
    recursive(100)
    iterate(100)
  }

  def recursive(n: Int) = {
    val start = java.lang.System.currentTimeMillis()
    for (i <- 0 to 10000000) {
      app.approximate(n)
    }
    println(java.lang.System.currentTimeMillis() - start)
  }

  def iterate(n: Int) = {
    val start = java.lang.System.currentTimeMillis()
    for (i <- 0 to 10000000) {
      app.approximateLoop(n)
    }
    println(java.lang.System.currentTimeMillis() - start)
  }

}

下面是執行結果,能夠看出遞歸調用的方式比循環方式慢了不少,並且有次數越多慢的幅度越大的傾向。 shell

922
969
2406
2032
13578
8047

咱們對approximate加上@tailrec註釋,從新編譯。這時編譯器給出了錯誤:app

  

error: could not optimize @tailrec annotated method approximate: it is neither private nor final so can be overridden def approximate(guess: Double): Double =

 

原來對於class中定義的函數,若是沒有聲明爲final或private,那麼編譯器是不會對尾遞歸進行優化的。

Scala在尾遞歸的優化上有諸多限制,好比遞歸必須是直接遞歸而不能是間接的,也不能是經過函數對象調用實現的遞歸,假如scala能突破這些限制,那是一件很是振奮人心的事

函數

相關文章
相關標籤/搜索