Event loop
Usually when your program has an indefinite lifecycle, you have an event loop at some point. Such a program has some state and handle some events. Whenever an event is occurred, the program handles it and provides a new state based on the event occurred and its arguments.
In Erlang you implement such an event loop with tail recursion and receive
statement.
We'll implement a sandboxed world which has a timeline and population counter. Every second our world will evolve updating its day and population counters:
-module(world).
-export([start/0]).
start() ->
loop(0, 100).
loop(Day, Population) ->
io:format("Day ~p, Population: ~p~n", [Day, Population]),
receive
Message ->
io:format("Message received: ~s~n", [Message]),
loop(Day, Population)
after 1000 ->
loop(Day + 1, round(Population * 1.2))
end.
Everything seems easy, right? However the program above isn't quite accurate. You see, the after
timeout is reset every time receive
statement receives a message. It won't fire every 1000 ms as you may expect. Instead, it fires after 1000 ms since the last message has been received. Let's redesign it so the world could evolve exactly once per 1000 ms. To do that we will use external timer on a different Erlang process:
-module(world).
-export([start/0, loop/2]).
start() ->
Loop = spawn(world, loop, [0, 100]),
timeline(Loop).
timeline(Loop) ->
receive
after 1000 ->
Loop ! evolve,
timeline(Loop)
end.
loop(Day, Population) ->
io:format("Day ~p, Population: ~p~n", [Day, Population]),
receive
{Message, Sender} ->
io:format("Message received: ~s~n", [Message]),
loop(Day, Population);
evolve ->
loop(Day + 1, round(Population * 1.2))
end.
Now one process generates update messages and the other updates the world upon the message arrival.