tovarchristian21

tovarchristian21

Optimized Tail Recursion

Hello guys, I’ve been wondering lately how do I actually know my recursive functions are in fact optimized and every recursive call is not generating a new stack. Is the arity of the function enough in order to know that a new stack has not been generated?

Thanks in advance.

Marked As Solved

Qqwy

Qqwy

TypeCheck Core Team

The tl;dr is: A function is tail recursive if the recursive call is the last thing the function does. If it first calls itself recursively and after that still does something else with the result, then it is not tail recursive and cannot be optimized away.

Also interesting is Tail Recursion is not a silver bullet which talks about Elixir/Erlang’s memory model (stack and heap growing towards each-other) which for many situation makes tail-recursion slightly less important than in some other languages.
Situations in which it remains very important are:

  • When reducing streams (whose length you don’t know; they might very well be more than can fit in memory).
  • When writing handler functions for processes. (if these are not tail-recursive, then the process will start hogging up more and more memory as time goes on). Note that when using GenServer-based processes, this is already managed for you behind the scenes.

Also Liked

rvirding

rvirding

Creator of Erlang

Just to point out that this optimisation not just done for tail-recursive calls but it is done for every tail-call. It is a TCO.

shanesveller

shanesveller

One of the simplest pitfalls that someone might intuitively write and end up breaking TCO is to include an operator outside of the final recursive call, but still on the final line, such as:

def recursive(0), do: 0

def recursive(n) when n > 1, do: 1 + recursive(n - 1) 

I’ve done it myself and see it in others’ code I’ve reviewed.

As a fun aside, if you’re on OSX, you can use time -l ... when running any shell command to observe small details about a limited set syscalls as well as memory allocations during the execution, and you can see that the above style uses more and more memory the larger argument you give it, but a properly TCO’d function will grow memory usage by argument size much more slowly, but still non-zero.


It doesn’t translate perfectly because our runtimes have such different semantics, but Haskell’s wiki article about their versions of fold-from-left and fold-from-right has some interesting insights here too, and I think the “visualization” they offer by expanding the math functions captures what I’m trying to express above pretty well:

foldr (+) 0 [1..1000000] -->
1 + (foldr (+) 0 [2..1000000]) -->
1 + (2 + (foldr (+) 0 [3..1000000])) -->
1 + (2 + (3 + (foldr (+) 0 [4..1000000]))) -->
1 + (2 + (3 + (4 + (foldr (+) 0 [5..1000000])))) -->
-- ...
-- ...  My stack overflows when there's a chain of around 500000 (+)'s !!!
-- ...  But the following would happen if you got a large enough stack:
-- ...
1 + (2 + (3 + (4 + (... + (999999 + (foldr (+) 0 [1000000]))...)))) -->
1 + (2 + (3 + (4 + (... + (999999 + (1000000 + ((foldr (+) 0 []))))...)))) -->

1 + (2 + (3 + (4 + (... + (999999 + (1000000 + 0))...)))) -->
1 + (2 + (3 + (4 + (... + (999999 + 1000000)...)))) -->
1 + (2 + (3 + (4 + (... + 1999999 ...)))) -->

1 + (2 + (3 + (4 + 500000499990))) -->
1 + (2 + (3 + 500000499994)) -->
1 + (2 + 500000499997) -->
1 + 500000499999 -->
500000500000
dimitarvp

dimitarvp

AppSignal’s article is explaining it quite okay.

Where Next?

Popular in Questions Top

marius95
Hello everyone, I try to use an Javascript Event Handler in my root.html.leex file. Therefore I created a function in the app.js file: ...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
LegitStack
I’m trying to make a websocket server in Phoenix or raw Elixir. I heard about gun, I think I could use cowboy, but since I’m not that sma...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
dblack
I’ve got an issue with an app and I’ve no idea of how to troubleshoot it. I’m hoping someone here might have seen something similar. I p...
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
yawaramin
In the Dialyzer docs ( dialyzer — OTP 29.0.2 (dialyzer 6.0.1) ), there is a way to turn off a specific warning for a function: -dialyzer...
New

Other popular topics Top

stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 43757 214
New
josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
New
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New

We're in Beta

About us Mission Statement