Lua
语言中有关函数的另一个有趣的特性是: Lua
语言是支持尾调用消除的。这意味着 Lua
语言可以正确地尾递归,虽然尾调用消除的概念并没有直接涉及递归。
尾调用( tail call
)是被当做函数调用使用的跳转。当一个函数的最后一个动作是调用另一个函数而没有再进行其他工作时,就形成了尾调用。例如,下列代码中对函数 g
的调用就是尾调用:
function f(x) x = x +1; return g(x) end
当函数 f
调用完函数 g
之后, f
不再需要进行其他的工作。这样,当被调用的函数执行结束后,程序就不再需要返回最初的调用者。因此,在尾调用之后,程序也就不需要在调用栈中保存有关调用函数的任何信息。当 g
返回时,程序的执行路径会直接返回到调用 f
的位置。在一些语言的实现中,例如 Lua
语言解释器,就利用了这个特点,使得在进行尾调用时不使用任何额外的栈空间。我们就将这种实现称为尾调用消除(tail-call elimination)。
由于尾调用不会使用栈空间,所以一个程序中能够嵌套的尾调用的数量是无限的。例如,下列函数支持任意的数字作为参数:
function foo(n) if n > 0 then return foo(n -1) end end
该函数永远不会发生栈溢出。
关于尾调用消除的一个重点就是如何判断一个调用是尾调用。很多函数调用之所以不是尾调用,是由于这些函数在调用之后还进行了其他工作。例如,下例中调用 g
就不是尾调用:
function f(x) g(x) end
这个示例的问题在于,当调用完 g
后,f在返回前还不得不丢弃 g
返回的所有结果。类似的,以下的所有调用也都不符合尾调用的定义:
return g(x) + 1 -- 必须进行加法 return x or g(x) -- 必须把返回值限制为1个 return (g(x)) -- 必须把返回值限制为1个
在 Lua
语言中,只有形如 return func(args)
的调用才是尾调用。不过,由于 Lua
语言会在调用前对 func
及其参数求值,所以 func
及其参数都可以是复杂的表达式。例如,下面的例子就是尾调用:
return x[i].foo(x[j] + a*b, i+j)