【发布时间】:2021-01-03 01:55:00
【问题描述】:
在Scala 中的函数式编程一书中,在解释如何在函数式编程中经常使用递归而不是命令式迭代时,作者通过使用称为“go”的辅助函数通过阶乘函数展示了递归”或“循环”,并声明这是标准做法是函数式Scala编程:
...
def factorial(n: Int): Int = {
@tailrec def go(n: Int, acc: Int): Int = {
if (n <= 0 ) acc
else go(n - 1, n*acc)
}
go(n, 1)
}
...但是如果不是更简洁地定义它,也可以很容易地定义它,而不需要辅助函数:
...
def factorial(n: Int): Int = {
if (n <= 0) 1
else n * factorial(n - 1)
}
我的理解是,通过利用堆栈框架并将返回值“传递”到前一个堆栈框架,可以在递归中实现累积值和避免突变。在这里,作者似乎出于类似目的使用了显式累加器参数。
使用辅助函数来累积这样的值是否有优势,或者他们是否使用此示例通过显式将状态传递给辅助函数来展示递归与命令式迭代的关系?
【问题讨论】:
-
因为tailrec(尾递归优化)。没有它,它会在某些时候出现堆栈溢出(这个问题在这个网站上多么讽刺)异常+它的效率要低得多
-
@ArtemSokolov 但第二个版本不会以堆栈溢出结束吗?它会达到基本情况。或者你的意思是如果没有注释你不能让编译器抱怨它不能做尾调用优化并且你不能把注释放在外部函数上?
-
尾递归不使用调用堆栈,而是使用累加器,因此可以消除前者。这种累加器风格的递归只是引擎盖下的一个循环。在懒惰的环境中,您可以选择懒惰的右弃牌。懒惰可以免费提高堆栈安全性。
-
@Aaron 注解不应用尾递归,它只会在未优化时强制编译器失败。第二个版本会因为 SO 而失败,因为没有 tailrec 优化,因为没有函数的尾递归结构。
-
啊,我明白了,在第二种情况下,递归调用后还有更多工作要做,因此编译器无法将其优化为循环,对吧?所以第一条评论应该是答案。如果你把它作为答案,我会这样标记它。
标签: scala recursion functional-programming tail-recursion