Long-lived processes in Elixir

One of the things that I loved about Elixir when I first started using the
language was the fact that everything runs inside a
process (an Erlang VM
process, not an operating system process). Each process is lightweight and
isolated, and creating and destroying them is fast. As such, Elixir’s processes
are front and center, which makes interacting with them both necessary and
wonderful.

For example, if you start the interactive console, iex, you’ll see that all
your commands are running in a process:

so all of the code you run in the console is running in process 84 (an
Elixir/Erlang process).

Naturally, you can choose to run the code in a separate process,

iex> spawn(fn -> 1 + 1 end)#PID<0.86.0>

spawn/1 creates a new process which runs the function provided, fn -> 1 + 1
end. Interestingly, we do not see the return value of the anonymous function
because the function ran in a different process. What we get instead is the
process id, pid, of the spawned process.

Notice moreover that I said that the anonymous function ran in another process
— ran as in past tense. Once process 86 finished doing all of its work, it
exited and we can no longer get any data from it.

As a developer you may have experienced (accidentally) creating an infinitely
recursive loop in a program, causing the program to hang for a while and
eventually crash. In our case we want to do this intentionally.

Let’s see it in action by writing a simple loop,

defmoduleLiveLongdodefrundorun()endend

That’s the simplest loop I can think of. Now in iex we can spawn a process
that uses that function,

In other languages, if you get stuck in an infinitely recursive loop, your program
hangs for a while and then crashes. Why doesn’t that happen here?

Elixir has tail call optimization (or really last call optimization). That
means that so long as we call the same function at the end of the execution
path, Elixir will not allocate a new stack frame to the call stack, and our
program will not suffer from a stack overflow.

It is true that you may not need a long-running process if all you need is to
run a script or perform a one-off task. But having long-running processes is key
to programming in Elixir. For example, it is how GenServers can send and
receive messages while keeping state, and it is how Supervisors can provide
fault tolerance to your system by knowing and restarting other processes that
crash.

In order to make this sink in even more, let’s create something that resembles a
simple GenServer - a process that is long-lived, can handle messages, and can
store state.

start/0 simply defines an empty list as the initial state and calls
receive_messages/1.

receive_messages/1 is where the magic happens. Here we use the receive/1
Elixir primitive to pattern match messages out of our mailbox. In our case, we
grab any message that comes in (msg) and pass it to our handle_message/2
function along with the state. The handle_message/2 function will do
something with the message and state, and we expect it to return an updated
state. We then perform the magic trick of making a recursive tail call. We
call receive_messages/1 with the new state.

handle_message({:store, value}, state) will simply pattern match when we
want to store a value, and it will add it to the current state.

handle_message({:send_all_values, pid}, state) receives a pid to which it
will send all the values found in the process (the current state).