正确的尾递归

lua-users home
wiki

Lua 5.0 实现 **正确的尾递归** [1]。这意味着对于一种特殊的函数调用模式,会进行优化以节省资源,特别是栈。这种模式要求每次函数以返回另一个函数的值(包括递归调用)结束时,当前的栈帧都会被重用,而无需消耗栈资源。用伪代码表示:

function f(args)
    ...
    return g(args2)
end

有时递归函数可以改写成尾递归版本。例如,阶乘函数的通常定义会得到:

function fact(n) if (n==0) then return 1 else return n*fact(n-1) end end

这不是尾递归,因为最后要进行的计算是 nfact(n-1) 的乘积。但下面这个是:

function factt(n) return fact_(n, 1) end

function fact_(n, ans) if (n==0) then return ans else return fact_(n-1, n*ans) end end

这两个定义在给定相同参数的情况下,结果完全相同。

> = fact(5)
120
> = factt(5)
120
但幕后情况有所不同。如果我们以非预期的方式使用这些函数,就会发现这种差异。上面的朴素实现只适用于正整数。如果给定负数或分数结果,它们可能会陷入无限递归。

如果我们执行 fact(-1),最终会得到一个栈溢出消息。

> = fact(-1)
stdin:1: stack overflow
stack traceback:
        stdin:1: in function `fact'
        stdin:1: in function `fact'
        ...
        stdin:1: in function `fact'
        (tail call): ?
        [C]: ?
另一方面,如果我们调用 factt(-1),它永远不会返回(这更类似于对函数体的数学分析)。

正确的尾递归是关于经济性,这对 Lua 所针对的应用类型非常重要。作为一种优化,调试变得更加困难,因为无法重建调用跟踪。

每当语言规范说实现了正确的尾递归时,就意味着承诺在尾函数调用的特殊情况下不会浪费栈。这是语言设计者为用户提供的质量契约。这在计算机科学界很常见:Scheme、Perl、Forth 等语言也这样做。如果你的最喜欢的编程语言没有这样做,请投诉。

这是一篇入门文本。它旨在说明该特性作为 Lua 的特性,并可能在函数调用教程中提到。


最近更改 · 偏好设置
编辑 · 历史
最后编辑于 2003 年 11 月 17 日凌晨 2:52 GMT (差异)