🎓 A1Learner
🔍
0 of 54 lessons completed

Elixir Project: Counter Server

A counter server is a process that holds one number and changes it only when it receives a message. This project builds that server, sends it a scripted run of messages, and prints the replies it sends back.

The loop answers one read with 2

The loop handles :inc and a read request. Two increments arrive before the read, so the reply is 2.

defmodule Counter do
  def loop(count) do
    receive do
      :inc ->
        loop(count + 1)

      {:read, sender} ->
        send(sender, {:count, count})
        loop(count)
    end
  end
end

counter = spawn(Counter, :loop, [0])
send(counter, :inc)
send(counter, :inc)
send(counter, {:read, self()})

count =
  receive do
    {:count, n} -> n
  end

IO.puts(count)

An add message moves the count by 5

The full loop also handles {:add, n}. An add of 5 and one increment give a reply of 6.

defmodule Counter do
  def loop(count) do
    receive do
      :inc ->
        loop(count + 1)

      {:add, n} ->
        loop(count + n)

      {:read, sender} ->
        send(sender, {:count, count})
        loop(count)
    end
  end
end

counter = spawn(Counter, :loop, [0])
send(counter, {:add, 5})
send(counter, :inc)
send(counter, {:read, self()})

count =
  receive do
    {:count, n} -> n
  end

IO.puts(count)

Two reads build a receipt list

The script reads the count twice during one run. The two replies form a labelled receipt list.

defmodule Counter do
  def loop(count) do
    receive do
      :inc ->
        loop(count + 1)

      {:add, n} ->
        loop(count + n)

      {:read, sender} ->
        send(sender, {:count, count})
        loop(count)
    end
  end
end

counter = spawn(Counter, :loop, [0])
send(counter, :inc)
send(counter, {:read, self()})

first =
  receive do
    {:count, n} -> n
  end

send(counter, {:add, 4})
send(counter, {:read, self()})

second =
  receive do
    {:count, n} -> n
  end

IO.inspect([first: first, second: second])

Example

defmodule Counter do
  def loop(count) do
    receive do
      :inc ->
        loop(count + 1)

      {:add, n} ->
        loop(count + n)

      {:read, sender} ->
        send(sender, {:count, count})
        loop(count)
    end
  end
end

counter = spawn(Counter, :loop, [0])
send(counter, :inc)
send(counter, :inc)
send(counter, {:add, 5})
send(counter, {:read, self()})

first =
  receive do
    {:count, n} -> n
  end

send(counter, {:add, 3})
send(counter, {:read, self()})

second =
  receive do
    {:count, n} -> n
  end

receipts = [first: first, final: second]
IO.inspect(receipts)
IO.puts(second)

Key points

  • State loop: a recursive loop function holds the count as its argument and calls itself with each new count.
  • Spawn: spawn(Counter, :loop, [0]) starts the counter with a count of 0.
  • Increment: the message :inc tells the loop to continue with the count plus 1.
  • Add: the message {:add, n} tells the loop to continue with the count plus n.
  • Read: the message {:read, sender} tells the loop to send {:count, count} back to the sender.
  • Receipts: replies collected in send order form a list that shows the count after each read.

In this example, the Counter loop holds the count and handles :inc, {:add, n}, and {:read, sender}, and each read replies with {:count, count}. The script starts the counter at 0, sends two increments and an add of 5, and reads the count back as 7. It adds 3 more and reads 10, then prints the labelled receipt list and the final count, 10.

Note

the counter keeps its state only in the argument of its recursive call, with no shared variable anywhere.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

The count is 6 and the server receives {:add, 4}. What count does the loop continue with?