Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does Elixir infinite recursion ever overflow the stack?

Tags:

A number of different how-tos on Elixir programming express the view that storing state or running an infinite loop is done idiomatically either by spinning the data off into an Agent or Task, or by infinite recursion of the function that needs state. They don't mention any limits on how deep the recursion can go or any other caveats.

Since searching for "Elixir stack overflow" just results in hits to this website, let me remove the ambiguity and ask here: What implementation guarantees are there in Elixir to make sure that infinite recursion as a method of 'looping' won't result in a stack overflow, especially when state information is being carried around along the way?

like image 604
bright-star Avatar asked Aug 23 '15 07:08

bright-star


People also ask

Does infinite recursion cause stack overflow?

The most-common cause of stack overflow is excessively deep or infinite recursion, in which a function calls itself so many times that the space needed to store the variables and information associated with each call is more than can fit on the stack.

Does infinite recursion make a program run forever?

In most programming environments, a program with an infinite recursion will not really run forever. Eventually, something will break and the program will report an error. Infinite recursion is the first example we have seen of a run-time error (an error that does not appear until you run the program).

What happens to the stack when your recursive function runs forever?

In some languages, your infinite recursive function will halt with a stack overflow based on system- or language-dependent conditions. The reason for this is that many implementations of function call and return allocate new space for each function call, and when space is exhausted the program will fail.

Can recursion occur infinitely?

Iteration and recursion can occur infinitely: An infinite loop occurs with iteration if the loop-continuation test never becomes false; infinite recursion occurs if the recursion step does not reduce the problem in a manner that converges on the base case.


1 Answers

To summarize excellent comments by Hristo, the general mechanism is called "Tail Call Optimization" (TCO) and it ensures that if the last thing a function does is invocation of another function (or itself), then there won't be stack push. Instead, a simple jump will occur.

There are some subtle nuances as to what is a tail call. Let's see a few example. The simplest one is:

def foo do
  # ...

  bar(...)  # tail call -> nothing is pushed to the stack
end

TCO will also apply for conditional expressions:

def foo do
  # ...

  if (...) do
    # ...
    bar(...)            # tail call
  else
    # ...
    baz(...)            # tail call
  end
end

This works because again the last thing a function does is an invocation of a function. The result of if is the result of either bar or baz so there's no need to push anything onto stack.

In contrast, if the caller function does something after calling another function, it's not a tail call, and TCO won't happen:

def foo do
  # ...

  # Not a tail call since we're doing something after bar returns
  # (increment the result by 1)
  1 + bar(...)    
end

Another example of breaking TCO is calling the function in try:

def foo do
  try do
    bar(...)    # not a tail call
  rescue
    # ...
  end
end

It's also worth mentioning that due to TCO you won't see some functions in the stack trace when an exception occurs:

def foo do
  # ...
  bar(...)  # at this point foo "disappears" from stack trace
end

def bar(...) do
  # ...
  raise("error")
end

The stack dump of this error won't include foo since it is not on the stack anymore (it is effectively replaced with bar).

like image 103
sasajuric Avatar answered Oct 21 '22 13:10

sasajuric