A few months ago, an article on Erlang in OFY reminded me that I
had never got around to exploring Erlang. Erlang is fascinating
because among other features, it was designed
to avoid catastrophic failure
to be able to scale to a system with a massive number of
processes
to communicate using asynchronous messages between processes
These features make it natural for Erlang to use multiple cores of
a processor. It also makes it easy to develop a distributed system.
You can get started with tutorials on the erlang.org site.
Learning the syntax of a new language is not hard. Since it is a
functional programming language, you may face a couple of challenges
when moving from an imperative or object oriented language.
You need to forget about loops and iteration and think
recursion. However, in Erlang, functions like foreach and map
make it easy, especially if you are already familiar with map
in Python.
You need to forget about variables, storing results in them
and manipulating them. This was the harder one for me.
Concurrent Programming
Even if you have not done the tutorials, you should not find it
hard to read Erlang code. So, consider a function, n_echo,
which echoes a message N times. Write the following code in a text
file, cp.erl:
% Module demos concurrent
processes
-module(cp).
-export([n_echo/2]).
n_echo(Msg, 0) ->
done;
n_echo(Msg,N) ->
io:format("~s~n",[Msg]),
n_echo(Msg, N-1).
The syntax of the format function is similar to the print function
in Python 3, with ~ replacing \.
In the Erlang shell(erl command), type the following
1> c(cp).
2> cp:n_echo(one, 3).
3> cp:n_echo(two, 3).
As you would expect, you get one printed 3 times followed
by two printed 3 times. In order to run these functions
concurrently, you can spawn processes. Define a convenience function,
Spawn_echo:
4> Spawn_echo = fun(Msg) ->
5> spawn(cp, n_echo, [Msg,3])
6> end.
The parameters of spawn are the module name, the function name and
the list of parameters. Now, you can use this convenience function
to spawn a list of processes:
7> lists:foreach(Spawn_echo,
[one, two, three]).
The foreach function will apply Spawn_echo function to each
element of the list. Now, you will find that one, two and
three are printed 3 times each but running concurrently.
Programming concurrency in Erlang is about as easy as it can be.
Now, consider the example of two programs, ping and pong.
Ping sends a message to pong and waits for a reply.
Pong is waiting for a message and responds after receiving a
message. Add these methods to the cp.erl file and modify the
export line as follows:
-export([n_echo/2, ping/3,
pong/0, ping_pong/0]).
ping(0, Pong_PID,Ping) ->
Pong_PID ! quit,
io:format("ping finished~n", []);
ping(N, Pong_PID,Ping) ->
Pong_PID! {Ping, self()},
receive
X ->
io:format("Ping received ~w~n",[X])
end,
ping(N-1, Pong_PID, Ping).
Pong() ->
receive
quit ->
io:format("Pong got quit~n",[]);
{X , Ping_PID} ->
io:format("Pong received
~w~n",[X]),
Ping_PID ! [X,pong],
pong()
end.
ping_pong() ->
Pong_PID =
spawn(cp, pong, []),
spawn(cp,ping, [3, Pong_PID,ping]).
Note how easy it is to send a message – Process ID ! <data>.
Data can be an atom, a tuple, a list, etc. The processing of a
receive statement is very much like a case statement. Depending upon
what is received, the appropriate code is executed.
The ping_pong function spawns both processes. Pong
is started first and its process id is passed to ping. Once
ping starts, it sends a message to pong along with its
process id.
Now, from Erlang shell,
8> c(cp).
9> cp:ping_pong().
Pong
received ping
Ping received [ping,pong]
...
In ping_pong, spawn twice as follows:
spawn(cp,ping, [3,
Pong_PID,ping]),
spawn(cp,ping, [3, Pong_PID,ping_ping]).
You should alter
pong so that it does not die after
receiving a quit message:
quit ->
io:format("Pong got quit~n",[]),
pong();
Now compile cp and run ping_pong again. You can notice that you
have the structure of a client/server program.
However, if the ping processes need to be run at various
occasions, it may not be possible to have the process id of the pong
process. Erlang provides an option to register a process. Other
processes can then use the symbolic reference. You will alter the
ping_pong function as follows:
ping_pong() ->
register(pong, spawn(cp, pong, [])),
spawn(cp, ping, [3,
ping]),
spawn(cp, ping, [3, ping_ping]).
Notice that you no longer pass the process id of pong to
ping. Ping will use the symbolic name pong to
send a message. The changes to the export statement and the ping
function will be as follows:
-export([n_echo/2, ping/2,
pong/0, ping_pong/0]).
ping(0, Ping) ->
pong !
Quit,
io:format("~w finished~n", [Ping]);
ping(N, Ping) ->
pong !
{Ping, self()},
receive
X ->
io:format("~w
received ~w~n",[Ping,X])
end,
ping(N-1, Ping).
With this brief exploration, you should be convinced that Erlang
is an excellent option for writing code to exploit multiple cores of
the current processors and will try it out. Learning a new language
is fun!
Nov 2013