总结Hristo的精彩评论,这种常规机制叫做“尾调用优化”(TCO),它确保如果一个函数最后执行的是调用另一个函数(或自身),那么不会进行栈推送,而是会发生简单的跳转。
对于何为尾调用存在一些微妙的细微差别。让我们看几个例子。最简单的例子是:
def foo do
# ...
bar(...) # tail call -> nothing is pushed to the stack
end
总成本拥有条件表达式也适用:
def foo do
# ...
if (...) do
# ...
bar(...) # tail call
else
# ...
baz(...) # tail call
end
end
这是因为函数最后做的事情又是对另一个函数的调用。 if
的结果是 bar
或 baz
的结果,所以不需要将任何东西压入堆栈。
相反,如果调用另一个函数后,调用者函数执行了其他操作,则不是尾调用,也不会发生 TCO:
def foo do
# ...
# Not a tail call since we're doing something after bar returns
# (increment the result by 1)
1 + bar(...)
end
另一个破坏尾调用优化的例子是在 try
中调用函数:
def foo do
try do
bar(...) # not a tail call
rescue
# ...
end
end
值得一提的是,由于TCO,在异常发生时,您将看不到堆栈跟踪中的某些函数:
def foo do
# ...
bar(...) # at this point foo "disappears" from stack trace
end
def bar(...) do
# ...
raise("error")
end
这个错误的堆栈转储不会包括foo
,因为它不再在堆栈上(它被有效地替换为bar
)。