This is a summary of a talk I held Monday May 14 2012 at an XP Meetup in Trondheim. It is meant as a teaser for listeners to play with Erlang themselves.
First, some basic concepts. Erlang has a form of constant called atom that is defined on first use. They are typically used as enums or symbols in other languages. Variables in Erlang are immutable so assigning a new value to an existing variable is not allowed
1> A = 1.
1
2> A = 2.
** exception error: no match of right hand side value 2
3> A = 1.
1
The third statement shows that the assignment is actually a pattern match, but the first statement assigns a value to the variable.
Lists and property lists are central in Erlang. The lists behave much like lists in other functional languages or arrays in other types of languages. Property lists are a special type of list that is used as a hash or dict in other languages
1> A = [1,2,3,4,5,6,7].
[1,2,3,4,5,6,7]
2> lists:nth(1, A).
1
3> lists:nth(7, A).
7
4> lists:last(A).
7
5>
5> length(A).
7
6> [Head|Tail] = A.
[1,2,3,4,5,6,7]
7> Head.
1
8> Tail.
[2,3,4,5,6,7]
The Erlang shell is useful and when defining a module, it can be compiled and run directly from the shell. Fibonacci numbers are an easy demonstration of the capabilities of a functional language.
-module(fib).
-export([fib/1]).
fib(0) -> 0;
fib(1) -> 1;
fib(N) -> fib(N - 1) + fib(N - 2).
Compile and test
1> c(fib).
{ok,fib}
2> lists:foreach(fun(N) -> io:format("fib ~p = ~p~n", [N, fib:fib(N)]) end, [1,2,3,4,5,6,7]).
fib 1 = 1
fib 2 = 1
fib 3 = 2
fib 4 = 3
fib 5 = 5
fib 6 = 8
fib 7 = 13
ok
Messaging
Erlang has a very powerful messaging system. This system supports distributed messaging.
First, a simple message loop that isn’t really a loop at all. Calling loop:init() will spawn a separate process waiting to receive messages.
-module(loop). | |
-export([init/0, loop/0, ping/1, stop/1]). | |
init() -> spawn(loop, loop, []). | |
ping(Pid) -> Pid ! {ping, self()}. | |
stop(Pid) -> Pid ! stop. | |
loop() -> | |
receive | |
{ping, From} -> From ! {pong, self()}; | |
stop -> ok | |
end. |
1> c(loop).
{ok,loop}
2> Pid = loop:init().
<0.39.0>
3> loop:ping(Pid).
{ping,<0.32.0>}
4> flush().
Shell got {pong,<0.39.0>}
ok
5> loop:ping(Pid).
{ping,<0.32.0>}
6> flush().
ok
The first time the loop is pinged, it replies pong, but the second time, nothing happens. When a message is received, the loop function will finish.
Timeouts
Message receive statements in Erlang may time out:
-module(loop1). | |
-export([init/0, loop/0, ping/1, stop/1]). | |
init() -> spawn(loop1, loop, []). | |
ping(Pid) -> Pid ! {ping, self()}. | |
stop(Pid) -> Pid ! stop. | |
loop() -> | |
receive | |
{ping, From} -> From ! {pong, self()}; | |
stop -> ok | |
after 1000 -> | |
io:format("I have decided to die~n") | |
end. |
c(loop1).
{ok,loop1}
2> Pid = loop1:init().
<0.39.0>
I have decided to die
The message loop times out after 1000 millisecond and exits the function.
An actual message loop
Let’s convert the message handling function loop into a real loop through tail recursion. Tail recursion means this loop can run forever without the growing stack otherwise caused by recursion.
-module(loop2). | |
-export([init/0, loop/0, ping/1, stop/1]). | |
init() -> spawn(loop2, loop, []). | |
ping(Pid) -> Pid ! {ping, self()}. | |
stop(Pid) -> Pid ! stop. | |
loop() -> | |
receive | |
{ping, From} -> | |
From ! {pong, self()}, | |
loop(); | |
stop -> ok | |
end. |
1> c(loop2).
{ok,loop2}
2> Pid = loop2:init().
<0.39.0>
3> loop2:ping(Pid).
{ping,<0.32.0>}
4> loop2:ping(Pid).
{ping,<0.32.0>}
5> flush().
Shell got {pong,<0.39.0>}
Shell got {pong,<0.39.0>}
ok
6> loop2:ping(Pid).
{ping,<0.32.0>}
7> flush().
Shell got {pong,<0.39.0>}
ok
Calling ping multiple times means we get multiple replies as we should from a message loop.
Calling stop terminates the message loop.
8> loop2:stop(Pid).
stop
9> loop2:ping(Pid).
{ping,<0.32.0>}
10> flush().
ok
State
While Erlang is a functional language and should be stateless, we may insert state into our message loop. Note that the variable State in the example never change value within a context.
-module(loop3). | |
-export([init/0, loop/1, ping/1, stop/1]). | |
init() -> spawn(loop3, loop, [0]). | |
ping(Pid) -> Pid ! {ping, self()}. | |
stop(Pid) -> Pid ! stop. | |
loop(State) -> | |
receive | |
{ping, From} -> | |
From ! {pong, self(), State}, | |
loop(State + 1); | |
stop -> | |
io:format("Final state = ~p~n", [State]), | |
ok | |
end. |
1> c(loop3).
{ok,loop3}
2> Pid = loop3:init().
<0.39.0>
3> loop3:ping(Pid).
{ping,<0.32.0>}
4> loop3:ping(Pid).
{ping,<0.32.0>}
5> loop3:ping(Pid).
{ping,<0.32.0>}
6> flush().
Shell got {pong,<0.39.0>,0}
Shell got {pong,<0.39.0>,1}
Shell got {pong,<0.39.0>,2}
ok
7> loop3:stop(Pid).
Final state = 3
stop
Every ping leads to an increment of State and stop prints the final value.
Distributed messaging
-module(loop4). | |
-export([init/0, loop/1, ping/1, stop/1]). | |
init() -> | |
Pid = spawn(loop4, loop, [0]), | |
register(loop, Pid). | |
ping(Node) -> | |
{loop, Node} ! {ping, self()}, | |
receive | |
{pong, From, State} -> | |
io:format("Got ~p from ~p~n", [From, State]) | |
after 1000 -> {error, timeout} | |
end. | |
stop(Node) -> {loop, Node} ! stop. | |
loop(State) -> | |
receive | |
{ping, From} -> | |
From ! {pong, self(), State}, | |
loop(State + 1); | |
stop -> | |
io:format("Final state = ~p~n", [State]), | |
ok | |
end. |
First, we start an Erlang shell with the short name left and a cookie meetup. The cookie is used by the shells to find each other.
erl -sname left -setcookie meetup
Then start the loop in this shell.
(left@localhost)1> c(loop4).
{ok,loop4}
(left@localhost)2> loop4:init().
true
Observe that the prompt includes the shortname of the node.
Start a new shell called right with the same cookie:
erl -sname right -setcookie meetup
Send messages to the loop running in the other shell and observe the response
(right@localhost)1> loop4:ping(left@localhost).
Got 0 from <6032.45.0>
ok
(right@localhost)2> loop4:ping(left@localhost).
Got 1 from <6032.45.0>
ok
Sending stop terminates the loop
(right@localhost)3> loop4:stop(left@localhost).
stop
This causes the following output in the left shell
Final state = 2
(left@localhost)3>
OTP or at least generic servers
It’s hard to talk about Erlang and messaging without at least touching OTP and generic servers. A module defining a generic server needs to specify -behaviour(gen_server) and define some functions used by the generic server framework. This file also introduces Erlang macros as a built-in macro ?MODULE is used here. ?MODULE contains the name of the module as an atom.
-module(gobbler). | |
-behaviour(gen_server). | |
-export([code_change/3, handle_call/3, handle_cast/2, handle_info/2]). | |
-export([init/1, start_link/0, terminate/2]). | |
-export([count/0, increment/0, stop/0]). | |
count() -> gen_server:call(?MODULE, count). | |
increment() -> | |
gen_server:cast(?MODULE, increment). | |
stop() -> gen_server:cast(?MODULE, stop). | |
start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). | |
code_change(_OldVsn, State, _Extra) -> {ok, State}. | |
handle_call(count, _From, State) -> {reply, State, State}; | |
handle_call(_Request, _From, State) -> {reply, ignored, State}. | |
handle_cast(increment, State) -> | |
{noreply, State + 1}; | |
handle_cast(stop, State) -> {stop, normal, State}; | |
handle_cast(_Request, State) -> {noreply, State}. | |
handle_info(_Info, State) -> {noreply, State}. | |
init(_) -> {ok, 0}. | |
terminate(_Reason, State) -> | |
error_logger:info_msg("End result is ~p~n", [State]). |
The functions representing an API to this server are count, increment, start_link and stop. We start the server by calling start_link and then call count and increment to see what happens.
1> c(gobbler).
{ok,gobbler}
2> gobbler:start_link().
{ok,}
3> gobbler:count().
0
4> gobbler:count().
0
5> gobbler:increment().
ok
6> gobbler:increment().
ok
7> gobbler:count().
2
No surprises there. When gobbler:count is called, gen_sever:call(?MODULE, count) sends the message count to the message loop of the server. The message loop calls handle_call(count, From, State) with From identifying the caller and State containing the State variable which is set to 0 in init called by start_link. handle_call(count, From, State) returns {reply, State, State} with reply being mandatory in a call to indicate that the loop should send a reply to the caller, the first State is the message returned to the caller and the last State is the new value of the State variable to send into the next iteration of the loop.
gobbler:increment uses gen_server:cast(?MODULE, increment) to send an increment message to the server. Cast sends the message and forgets it, meaning the caller will not expect a reply or even wait for one.
Selection of the right handle_cast is a result of pattern matching. increment matches the first handle_cast so it is executed. A cast of stop would have matched the second one.
Err, second paragraph:
“Variables in Erlang are *mutable* so assigning a new value to an existing variable is not allowed”
Should read:
“Variables in Erlang are *immutable* so assigning a new value to an existing variable is not allowed”
Next paragraph: “first match assigns a value to the variable.” -> “first statement assigns a value to the variable.”
A bit further on: “Fibonacci is always a nice way to show things in a functional language.” -> “Fibonacci numbers are an easy demonstration the capabilities of a functional language.”
And I would put in the URL to the Wikipedia articles on Fibonacci numbers, http://en.wikipedia.org/wiki/Fibonacci_number. Most of your readers will know the sequence but some may not or may not remember it clearly.
Sorry, had to stop here to write reviews and some blog posts. Will be mentioning your post as a resource on Erlang on my blog.
Thanks a lot for those corrections. Looks like I was in too much of a hurry writing this down. I have corrected the errors you pointed out and fixed a couple of more that I found now as well.