Erlang Example: Star Messages

May 29, 2008 - 3 minute read -
erlang exercises

This is part of a series on the Erlang Exercises which is a great set of programming problems that challenge you to implement solutions to some common Erlang problems. I'm going to share some of my solutions to these problems.

Interaction between processes, Concurrency

3) Write a function which starts N processes in a star, and sends a message to each of them M times. After the messages have been sent the processes should terminate gracefully.

-module(star).
-export([start/2, loop/0, sendMessage/2]).
start(0, _) ->
    io:format("done~n", []);
start(N, M) ->
    timer:sleep(random:uniform(10) * 100),
    LoopPid = spawn(?MODULE, loop, []),
    spawn(?MODULE, sendMessage, [M, LoopPid]),
    start(N - 1, M).
sendMessage(0, Pid) ->
    Pid ! done;
sendMessage(M, Pid) ->
    timer:sleep(random:uniform(5) * 100),
    Pid ! M,
    sendMessage(M - 1, Pid).
loop() ->
    receive
        done ->
            io:format("* Process: ~10w, Message#: terminate!~n", [self()]);
        R ->
            io:format(": Process: ~10w, Message#: ~w ..~n", [self(), R]),
            loop()
    end.

Explanation

start(N, M) The entry point to the code defines the Number of processes to start and the number of Messages to pass. timer:sleep(random:uniform(10) * 100), is just to demonstrate that things are actually happening concurrently. Without this the program usually runs to fast and everything is sequential. LoopPid = spawn(?MODULE, loop, []), creates a new process running the loop function. That process is set up to receive messages and print some information out. spawn(?MODULE, sendMessage, [M, LoopPid]), is then called to create a new process whose job it is to send each LoopPid (really the nodes in the star) M number of messages. Finally start(N - 1, M). is called tail recursively with one less Node count.

start(0, _) This function is called or matched when the tail recursion from start(N, M) finally gets down to the value where N is zero. In that case we no longer want to spawn processes so we just write "done" and exit the function.

sendMessage(M, Pid) Following a similar pattern to the start function this counts down M to zero and sends M as a message to the given Pid. The tail recursion handles decrementing to the count of M until the pattern (0, Pid) is matched at which point sendMessage(0, Pid) is matched and the Pid is sent the 'done' message.

loop() This is the function that is run as the individual processes or the "points of the star". Its job is to receive messages passed to it. When done -> is matched by a message, the process will print out some information and then end gracefully. R -> represents a message being passed with a single value that is anything other than the atom 'done'. When it is matched the value is printed and the process waits for another message by calling back to itself with loop()

Thoughts

One of the interesting things to me is the tail recursion of Erlang and other Functional Programming Languages. This accomplished the same thing as a looping construct without using for or while or any of those usual imperative methods.