【发布时间】:2017-09-12 02:48:27
【问题描述】:
我正在尝试理解 Haskell 中的尾递归。我想我了解它是什么以及它是如何工作的,但我想确保我没有把事情搞砸。
这是“标准”阶乘定义:
factorial 1 = 1
factorial k = k * factorial (k-1)
在运行时,例如factorial 3,我的函数将调用自身 3 次(给予或接受)。如果我想计算阶乘 99999999,这可能会造成问题,因为我可能会出现堆栈溢出。到达factorial 1 = 1 后,我将不得不“返回”堆栈并乘以所有值,因此我有 6 个操作(3 个用于调用函数本身,3 个用于乘以值)。
现在我向您介绍另一种可能的阶乘实现:
factorial 1 c = c
factorial k c = factorial (k-1) (c*k)
这个也是递归的。它会调用自己 3 次。但它不存在然后仍然必须“回来”来计算所有结果的乘法的问题,因为我已经将结果作为函数的参数传递了。
据我所知,这就是尾递归。现在,它似乎比第一个好一点,但您仍然可以轻松地发生堆栈溢出。我听说 Haskell 的编译器会在后台将 Tail-Recursive 函数转换为 for 循环。我想这就是为什么做尾递归函数有回报的原因?
如果这是原因,那么如果编译器不打算做这个聪明的把戏,那么绝对没有必要尝试使函数尾递归——我说的对吗?例如,尽管理论上 C# 编译器可以检测到尾递归函数并将其转换为循环,但我知道(至少我听说过)目前它还没有这样做。因此,如今使函数尾递归绝对没有意义。是这样吗?
谢谢!
【问题讨论】:
-
只是指出“标准”阶乘定义是
factorial 0 = 1 -
是的,我想到了,但是阶乘 1 = 1 更有效。
-
您知道,保存单步迭代可能是计算阶乘时要担心的最后件事。另外,如果您尝试计算 99999999!我很确定堆栈溢出将是您遇到的最少的问题。
-
计算 999999!不是线程的重点 - 停止吹毛求疵。
-
foldl 是左关联的并且是尾递归的:
foldl ◦ b [x1, x2, x3, ..., xk ] = (...(((b ◦ x1) ◦ x2) ◦ x3) ◦ ...) ◦ xk而 foldr 是右关联的不是尾递归的:foldr ◦ b [x1, x2, x3, ..., xk ] = x1 ◦ (x2 ◦ (x3 ◦ (...(xk ◦ b)...)))
标签: haskell recursion tail-recursion