jonnifyelixir
F3 / F3.07 / state

F3.07 · part 3 of 3

Holding state in a loop

A process has no variables that outlive a single message. What it has is a recursive loop: after handling a message, the process tail-calls itself with updated state. The state is the loop's argument, living in a function that never returns. This recursive receive is exactly what GenServer abstracts in F3.08.

The portal runs a tally process that counts events. It loops on a count, answers :inc and {:get, from}, and carries the running total forward. Step through three messages and watch the state move through the loop.

State through the loop

Each turn of the loop receives one message, computes the next state, and recurses with it. An :inc raises the count; a {:get, from} replies and keeps the count the same. Step through the sequence and read the state in, the clause taken, and the state passed on.

Message in the sequence · select one

STATE IN · loop(count) count = 0 MESSAGE RECEIVED :inc CLAUSE TAKEN :inc -> loop(count + 1) {:get, from} -> send(from, count); loop(count) NEXT LOOP loop(1) REPLY —

      

Nothing mutates. Each message produces a fresh state value handed to the next call, and the process stays alive because the loop never returns — it only recurses.

The whole server

Start the loop with an initial state, then drive it with messages. The recursion is in tail position, so the process holds steady memory no matter how many messages it handles. This is a hand-written GenServer.

def loop(count) do
  receive do
    :inc         -> loop(count + 1)
    {:get, from} -> send(from, count); loop(count)
  end
end

counter = spawn(fn -> loop(0) end)   # starts holding 0
send(counter, :inc)
send(counter, :inc)
send(counter, {:get, self()})
receive do n -> n end                  # => 2

The idea

Remember across messages without mutable variables — carry the memory forward by hand.

In Elixir

State is the loop's argument; tail-recursion keeps it endless. GenServer hides the loop and the receive behind callbacks.

That completes processes: spawn, message passing, and a stateful loop — the actor model in full. Next module: F3.08 — OTP: GenServer & supervisors, where this loop becomes handle_call/3 and a supervisor restarts the process when it crashes.

References

Primary sources for this lesson, and where it connects in the course.