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

时间:2015-08-23 07:46:26

标签: stack-overflow elixir

Elixir编程的A numberdifferent how-tos表达了这样的观点:存储状态或运行无限循环是通过将数据旋转到代理或任务中,或者通过函数的无限递归来惯用地完成的需要国家。他们没有提到任何限制递归的深度或任何其他警告。

自搜索" Elixir堆栈溢出"只是导致对这个网站的点击,让我消除歧义,并在这里问:Elixir有哪些实现保证,以确保无限递归作为循环的方法'不会导致堆栈溢出,尤其是在沿途传输状态信息时?

1 个答案:

答案 0 :(得分:16)

总结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)。