Elixir无限递归是否会溢出堆栈?



关于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的结果是barbaz的结果,因此不需要将任何东西压入堆栈。

相反,如果调用函数在调用另一个函数之后做了一些事情,那么它不是尾部调用,并且不会发生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)。

最新更新