我想为尾部递归优化一些函数。该函数将在没有优化的情况下发出stackoverflow异常。
示例代码:
import scala.util.Try
import scala.annotation.tailrec
object Main {
val trials = 10
@tailrec
val gcd : (Int, Int) => Int = {
case (a,b) if (a == b) => a
case (a,b) if (a > b) => gcd (a-b,b)
case (a,b) if (b > a) => gcd (a, b-a)
}
def main(args : Array[String]) : Unit = {
testTailRec()
}
def testTailRec() {
val outputs : List[Boolean] = Range(0, trials).toList.map(_ + 6000) map { x =>
Try( gcd(x, 1) ).toOption.isDefined
}
outputTestResult(outputs)
}
def outputTestResult(source : List[Boolean]) = {
val failed = source.count(_ == false)
val initial = source.takeWhile(_ == false).length
println( s"totally $failed failures, $initial of which at the beginning")
}
}
运行它将产生以下输出:
[info] Running Main
[info] totally 2 failures, 2 of which at the beginning
因此,前两次运行在没有优化的情况下执行,并且由于stackoveflow异常而被中途丢弃,并且只有稍后的调用才会产生所需的结果。
有一种解决方法:在实际使用之前,您需要使用假运行来预热该功能。但它看起来很笨拙,非常不方便。还有其他方法可以确保我的递归函数在第一次运行之前针对尾递归进行优化吗?
我被告知使用两步定义
@tailrec
def gcd_worker(a: Int, b: Int): Int = {
if (a == b) a
else if (a > b) gcd(a-b,b)
else gcd(a, b-a)
}
val gcd : (Int,Int) => Int = gcd_worker(_,_)
如果可能的话,我更喜欢保持干净的功能风格定义。
答案 0 :(得分:3)
我认为@tailrec
根本不适用于定义为val
的函数。将其更改为def
,它将无误运行。
答案 1 :(得分:2)
根据我的理解@tailrec
[1]需要在方法而不是字段上。通过进行以下更改,我能够在REPL中对此进行尾递归:
@tailrec
def gcd(a: Int, b: Int): Int = {
if (a == b) a
else if (a > b) gcd(a-b,b)
else gcd(a, b-a)
}
[1] http://www.scala-lang.org/api/current/index.html#scala.annotation.tailrec