总体上学习Scala和函数式编程。在以下尾递归阶乘实现中:
def factorialTailRec(n: Int) : Int = {
@tailrec
def factorialRec(n: Int, f: => Int): Int = {
if (n == 0) f else factorialRec(n - 1, n * f)
}
factorialRec(n, 1)
}
我想知道用 value 调用第二个参数相对于 name 调用第二个参数是否有好处(就像我所做的那样)。在第一种情况下,每个堆栈框架都会负担产品。在第二种情况下,如果我的理解是正确的,那么整个产品链将在第if ( n== 0)
个堆栈帧处转移到案例n
,因此我们仍然必须执行相同数量的乘法。不幸的是,这不是形式a ^ n的乘积,可以通过重复平方来以log_2n的步长进行计算,而是每次相差1的项的乘积。因此,我看不到任何优化最终产品的可能方法:它仍然需要O(n)项的乘法。
这是正确的吗?就复杂性而言,按值调用等同于按名称调用吗?
答案 0 :(得分:2)
让我稍微扩展一下您已经在评论中告诉您的内容。 这就是编译器使用别名参数的方式:
@tailrec
def factorialTailRec(n: Int, f: => Int): Int = {
if (n == 0) {
val fEvaluated = f
fEvaluated
} else {
val fEvaluated = f // <-- here we are going deeper into stack.
factorialTailRec(n - 1, n * fEvaluated)
}
}
答案 1 :(得分:0)
通过实验,我发现通过按名称命名的调用,该方法变为... 非尾递归!我编写了以下示例代码,以递归方式比较阶乘尾部和非尾部阶乘:
package example
import scala.annotation.tailrec
object Factorial extends App {
val ITERS = 100000
def factorialTailRec(n: Int) : Int = {
@tailrec
def factorialTailRec(n: Int, f: => Int): Int = {
if (n == 0) f else factorialTailRec(n - 1, n * f)
}
factorialTailRec(n, 1)
}
for(i <-1 to ITERS) println("factorialTailRec(" + i + ") = " + factorialTailRec(i))
def factorial(n:Int) : Int = {
if(n == 0) 1 else n * factorial(n-1)
}
for(i <-1 to ITERS) println("factorial(" + i + ") = " + factorial(i))
}
观察到内部tailRec
函数按名称调用第二个参数。 其@tailRec
批注仍然不会引发编译时错误!
我一直在为ITERS
变量使用不同的值,对于100,000,我收到一个... StackOverflowError
!
(由于Int
的溢出,结果为零。)
所以我继续将factorialTailRec/2
的签名更改为:
def factorialTailRec(n: Int, f: Int): Int
即按值调用参数f
。这次,main
的运行factorialTailRec
的部分完全可以正常运行,而factorial/1
当然也以完全相同的整数崩溃了。
非常非常有趣。似乎在这种情况下,按名称进行调用会保留堆栈帧,因为需要对产品本身进行计算,一直返回到调用链。