If you are learning erlang, you might run into Bill Clementson's Beer Song, as it is currently on the front page of trapexit.org (the erlang community site).
Having only written a couple simple erlang programs, I was at first confused by the program. In learning exactly how the program worked, I removed the verse generated code (since for bears, counting is hard enough) and added a bunch of debug statements.
Go read Bill's version, then you can see mine.
-module(bearsong).
-export([start/0]).
start() ->
clear(),
lists:foreach(fun spawner/1, lists:seq(0,3)),
countdown(1),
countdown(2).
% spawner creates a new process that sends a message with a specific number
spawner(Num) ->
Pid = self(),
NewPid = spawn(fun() -> io:format("sending ~p message ~p~n", [Pid, Num]), Pid ! Num end),
io:format("spawner(~p) ~p spawned ~p~n", [Num, Pid, NewPid]).
% countdown from Num to 0.
% countdown listens for a message with the current value of Num, if
% it doesn't receive it, it will ask spawner to create a new message
% with it, and then recall countdown with the same value.
countdown(Num) ->
io:format("countdown(~p) ~p ", [Num, self()]),
receive
Num ->
io:format("ok~n"),
if
Num > 0 -> countdown(Num-1);
true -> ok
end
after 100 ->
io:format("timeout~n"),
spawner(Num),
countdown(Num)
end.
% helper function to remove all queued messages
clear() ->
receive
_ -> clear()
after 100 -> ok
end.
Running my version produces the following output:
spawner(0) <0.30.0> spawned <0.52.0> sending <0.30.0> message 0 spawner(1) <0.30.0> spawned <0.53.0> sending <0.30.0> message 1 spawner(2) <0.30.0> spawned <0.54.0> sending <0.30.0> message 2 spawner(3) <0.30.0> spawned <0.55.0> sending <0.30.0> message 3 countdown(1) <0.30.0> ok countdown(0) <0.30.0> ok countdown(2) <0.30.0> ok countdown(1) <0.30.0> timeout spawner(1) <0.30.0> spawned <0.56.0> sending <0.30.0> message 1 countdown(1) <0.30.0> ok countdown(0) <0.30.0> timeout spawner(0) <0.30.0> spawned <0.57.0> sending <0.30.0> message 0 countdown(0) <0.30.0> ok
In Bill's version I was confused by his use of spawn in the spawner function. The process being spawned is a function that sends a message to a Pid, the Pid being generated by a self() call in the function. Coming from JavaScript, I had expected self to refer to the context of the function. I didn't expect self() to have the same value in spawner as it did in countdown. Self seems to be the Pid of the process (perhaps implicit) as we see spawner and countdown reporting the same pids.
The second problem I had was why he was using spawn in the first place. Spawn in this instances seems to act like setTimeout(function(){..}, 0) in JavaScript. I was trying to read more into it than delayed execution (combined with the confusion of a shared Pid above.) The program runs the same if you send the message inline instead of spawning a new process to execute the function which sends the message.
While I still don't understand the benefit of spawning new processes to send messages, I do understand how it is works. A recursive function is trying to countdown, it blocks on receiving a message with the current number, and if it doesn't receive a message, it will ask the spawner to resend a new message with the number so it can receive it and continue.
Responses to "Erlang Bear Songs"
Leave a response