Let's start by seeing the first two lines.
-module(pingpong).
-compile(export_all).
The first is the module declaration. Your argument is an atom (a non-inverted, lowercase word) that represents the name given to the module. Adapted from Learn You Some Erlang :
-module(Name).
This is always the first attribute (and first sentence) of a file, and with good reason: it is the name of the current module, where Name
is an atom. This is the name that you will use to call functions from other modules. Calls are made in M:F(A)
, where M
is the name of the module, F
is the function, and A
its arguments.
The second sentence tells the compiler to make public all the functions defined in the module, that is, the entire F function writing to this module can be called from outside, with pingpong:F
. This simplifies the learning process but, in general, it is not good practice to make all functions public. Instead, enumerate each function you want to export.
Let's now look at the defined functions.
start_pong() ->
register(pong, spawn(pingpong,pong,[])).
This is possibly the entry point to your code. Compile the module, and start by calling pingpong:start_pong().
in the Erlang console, on an instance of the virtual machine (a node). What this function does is register the name pong
as identifier for a new process that will be created with spawn
.
Therefore, spawn
creates Erlang processes. spawn
is a built-in function, so it does not require you to type the module name in the prefix. Your arguments are spawn(Modulo, Funcao_Exportada, Lista_de_Argumentos)
, as seen in the documentation .
Returning to start_pong
, what it actually does is create a process that will run the pong
function of this module, without arguments, and call this process pong
.
pong() ->
receive
finished ->
io:format("Pong finished ~n");
{ping, Ping_Pid} ->
io:format("i am the receiver ~n"),
Ping_Pid ! pong,
pong()
end.
The new start_pong
process will perform this function. The entire Erlang process has its own mailbox . The processes communicate with each other leaving messages in the other boxes. Messages can be just about anything, any data.
The new process enters the block receive
, which tells you to search for messages in your mailbox, or to wait until there is some. Then use pattern matching to find the action corresponding to the received message. If you are accustomed to common imperative languages, you can see this almost as a switch .
If the process has a message consisting of the atom finished
, it prints "Pong finished" on the console and ends. If the process has a message that is a pair consisting of the atom ping
and a process identifier ( pid - the whole process has its own), then it will execute the remaining code of this function. p>
The Ping_Pid
, beginning with a capital letter, tells Erlang to save to a variable with this name whatever the value that comes in the second element of the message. In this case, we're just waiting for a pid .
When you enter this case, print "i am the receiver" and send a message with the atom pong
to the process identified by Ping_Pid
- this is the utility of the !
operator. Finally, the function is called recursively, to go back to the mailbox.
The next thing you will write to the console, probably in another instance of the virtual machine, is the call to start_ping
.
start_ping(Pong_Node) ->
spawn(pingpong, ping, [3, Pong_Node]).
As seen before, what this does is create a new process, which will run the ping
function with arguments 3
and Pong_Node
, where the second is the node where the first process is running.
ping(0, Pong_Node) ->
{pong, Pong_Node} ! finished,
io:format("Pong finished ~n");
ping(N, Pong_Node) ->
{pong, Pong_Node} ! {ping, self()},
receive
pong ->
io:format("i am the sender ~n")
end,
ping(N-1,Pong_Node).
This function is defined in two cases (note that the first definition of ping
ends with ;
, instead of .
- this tells Erlang that there is more to complete the definition of this function.)
The function is called with 3
as the first argument. Since 3
does not equal 0
, the process executes the second case, with N
as argument.
This process sends {ping, self()}
to the given process by {pong, Pong_Node}
, which follows the {nome_registado, nome_do_nodo}
syntax. The self()
function is used to get the pid of the current process. After this, the process waits for response, and repeats this cycle while N
is greater than zero.
When
N
reaches zero, the first case is executed, sending
finished
to
{pong, Pong_Node}
, and ending execution.
If you find this explanation incomplete, you can also take a look at in the tutorial , which explains this same program.