Erlang Introduction (For the Ruby Guy) part 3
Sunday, August 17, 2008 at 02:04PM Processes
In Erlang you world revolves around processes. Not to be confused with OS processes. Here we are talking about highly light weight threads that can quickly be started and shut down. These are a lot faster and lighter that threads you may be used to from Ruby and you will use them quite a lot more than usually happens in Ruby. The best part is that this great power of Erlang is one of the simplest things to do. You just simply use the spawn(module, function, [parameters]) command to start a function and it starts it as a process. Really thats it. Lets see an example of this by starting the recurse command from part 2: P1 = spawn(demo,recurse,[[1,2,3]]).. Now this process started and ended within a second but imagine if this would have been some call to some webserver taking up to a few seconds. There the process would have done it's thing and the creating function continues doing it's thing without having to hang around for results.
This is how you should do your Erlang programs. You should avoid as much as you can having your program waiting for some action to complete. Instead you should have a bunch of processes communicating with each other.
Sending and Receiving Messages
Now. It's not enough in most cases to just spawn the process and let it run until it finishes. Often you must talk to that process and as they do not share memory with each other they need to talk to each other by passing messages. Luckily this also is as simple as it gets.
The return value of a spawn function is the Pid of the Process. In the example before we bound the Pid to variable P1. We then could just send a message to it simply by saying P1 ! [1,2,3,4].. If we expect a response back we often send our own Pid within the message and we can get the Pid of current process with the command self(). In that case the message might look like P1 ! {helloworld, self()}..
It's no use sending a message if the receiving process is not listening. Enter the receive consturct. The receiver waits for a message and when it receives that message it tries matching it to a pattern and runs the corresponding actions. Lets look at a receive block.
receive
[Head|TheRest] ->
io:format("Got a list with the head: ~p ~n",[Head]);
{helloworld, CallingPid} ->
io:format("Got a hello world. Saying hello back. ~p ~n"),
CallingPid ! {response, "Hi Back"};
MatchAll ->
io:format("Error: Dont know what do do with ~p ~n",[MatchAll])
end.
Here we have 3 possible messages to receive. This works in general much like when we define multiple versions of the same function.
Registering
One thing that I will mention before giving an example of using processes is the concept of registering process under a name. Often it's not convienient to store a Pid in a variable since variables only exist within a function. There we register the Pid under a name with the command register(Name, Pid). For example we could call register(servername, P1). and from now on we can always make the call servername ! {helloworld, self()}. from anywhere inside that node.
A Simple Example of Processes
Lets take a look at an example code. Lets open our demo.erl from Part 2 and add start_server/0, start_server/0, remote_convert/1, server_loop/0 to the exports. Go to the end of the file and enter the following code:
start_server() ->
Pid = spawn(demo, server_loop, []),
register(converter, Pid).
stop_server() ->
converter ! shutdown
unregister(converter).
server_loop() ->
receive
{convert, cm, Value, CallingPid} ->
CallingPid ! {inch, Value / 2.54},
server_loop();
{convert, inch, Value, CallingPid} ->
CallingPid ! {cm, Value * 2.54},
server_loop();
shutdown ->
true;
MatchAll ->
io:format("Got a message I don't understand. ~n"),
server_loop()
end.
remote_convert({Unit, Value}) ->
converter ! {convert, Unit, Value, self()},
receive
{NewUnit, NewValue} ->
io:format("Result: ~p ~p~n", [NewUnit,NewValue]);
error ->
ok
end.
Lets take a look what happens here. We start by compiling and running demo:start_server(). that spawns server_loop/0 as a process and registers the Pid as converter. Next up is stop_server/0 that sends a message to the server asking it to shutdown. Third is server_loop/0 has a receive block that waits for 4 matching messages. First 2 are tuples requesting a unit conversion. It converts and sends a message back and then calls itself so it can wait for the next message. The shutdown message really does nothing except not calling for the loop to repeat. MatchAll will handle all other messages and write out an error.
remote_convert/1 then manages calling the server and then waits for a message back. Lets try it out by running demo:remote_convert({inch,1}). and you should get a message back.


Reader Comments (1)
Thank you for the tutorial.
In your last paragraph you said Lets try it out by running demo:convert({inch,1}), it should demo:remote_convert({inch, 1}).