Jim

Jim

Endless recursion

I’m new to Elixir, and from what I read heavy use of recursion is mandatory. With that in mind, I look at this simple blink example I found:

defmodule FlightController do
  use Application

  @blink_duration 150 # ms
  @led_pin 18
  @gpio_on 1
  @gpio_off 0

  def start(_type, _args) do
    {:ok, pid} = Gpio.start_link(@led_pin, :output)

    spawn fn -> blink_forever(pid) end

    {:ok, self}
  end

  def blink_forever(pid) do
    Gpio.write(pid, @gpio_on)
    :timer.sleep @blink_duration
    Gpio.write(pid, @gpio_off)
    :timer.sleep @blink_duration

    blink_forever(pid)
  end
end

In my non-Elixir experience, recursive programs build a new stack with each call and release it when the last recursive call returns. You can’t do it forever because you’ll be out of stack space. So, this example couldn’t work on another platform. I have not run it but I’m guessing it does work.

My question, then, is, how does Elixir / BEAM implement recursion?

Most Liked Responses

rvirding

rvirding

Creator of Erlang

Actually the tail call optimisation is done for all last calls not just recursive calls. It is sometimes called last-call optimisation. So you can write:

defmodule Foo do
  def bar() do
    baz()
  end

  def baz() do
    bar()
  end
end

which will happily recurse forever without building stack. It also works with inter-module calls.

This is the classic way of implementing state machines: you change state by calling the next state function as a tail call. You could view the Foo module as implementing a state machine with 2 states bar and baz.

peerreynders

peerreynders

Joe Armstrong: [erlang-questions] Tail call optimization

The correct name for the optimization used in Erlang is “last call optimization”.

cmkarlsson

cmkarlsson

The BEAM uses Tail Call Optimization (TCO) to avoid blowing the stack.

As an example:

def fac(1), do: 1
def fac(n), do: n * fac(n -1)

is not TCO because the recursive can only be done when it is the last operation of the function. In the fac function above it needs to keep the frame to do the multiplication.

This can be re-written in a TCO fashion by using an accumulator to hold the result.

def fac(n), do: fac(n, 1)

def fac(1, result), do: result
def fac(n, result), do: fac(n - 1, n * result)

In your case the blink_forever(pid) call is the last in the function and hence will use TCO.

Where Next?

Popular in Questions Top

_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
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
Tee
can someone please explain to me how Enum.reduce works with maps
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
beno
I will often find my self writing things similar to: case some_value do nil -> something() "" -> something() _ -> somethi...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: The documentation above suggests that while ...
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

Other popular topics Top

aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
johnnyicon
Hi all, I’ve just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I’m trying to use Postgres...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
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
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
hariharasudhan94
Lets say i have map like this fetching from my database %{"_id" => #BSON.ObjectId<58eb1a7a9ad169198c3dXXXX>, "email" => "XXX...
New

We're in Beta

About us Mission Statement