关于Elixir编程的许多不同的操作方法表达了这样一种观点,即存储状态或运行无限循环是通过将数据旋转到Agent或Task中,或者通过对需要状态的函数进行无限递归来完成的。他们没有提到任何关于递归深度的限制或任何其他警告。
既然搜索"Elixir堆栈溢出"只会导致这个网站的点击,那么让我消除歧见并在这里问:Elixir中有什么实现保证来确保无限递归作为一种"循环"方法不会导致堆栈溢出,特别是当状态信息在此过程中被携带时?
总结一下Hristo的优秀评论,一般机制被称为"尾部调用优化"(TCO),它确保如果函数所做的最后一件事是调用另一个函数(或其自身),则不会有堆栈推送。相反,将发生一个简单的跳转。
什么是尾语有一些微妙的差别。让我们看几个例子。最简单的是:
def foo do
# ...
bar(...) # tail call -> nothing is pushed to the stack
end
TCO也适用于条件表达式:
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
另一个破坏TCO的例子是调用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
)。