Erlang Exercises: Ring Messages

May 28, 2008 - 3 minute read -
erlang exercises

Erlang Exercises has a great little series of programming problems that challenge you to implement solutions to some common Erlang problems. It's a great way to learn the language. I'm going to share some of my solutions to these problems. Maybe for some discussion, maybe for some feedback from some people more experienced with Erlang, maybe just to give some new people a flavor of the language.

Interaction between processes, Concurrency

2) Write a function which starts N processes in a ring, and sends a message M times around all the processes in the ring. After the messages have been sent the processes should terminate gracefully.

-module(ring).
-export([start/2, loop/2]).
start(N, M) ->
    Seq = lists:seq(1, N),
    Messages = lists:reverse(lists:seq(0, M-1)),
    LastP = lists:foldl(fun(S, Pid) ->
                                             spawn(?MODULE, loop, [N-S, Pid])
                             end, self(), Seq),
    spawn(fun() -> [ LastP ! R || R <- Messages ] end).
loop(N, NextPid) ->
    receive
        R when R > 0 ->
            NextPid ! R,
            io:format(": Process: ~8w, Sequence#: ~w, Message#: ~w ..~n",
                          [self(), N, R]),
            loop(N, NextPid);
        R when R =:= 0 ->
            NextPid ! R,
            io:format("* Process: ~8w, Sequence#: ~w, Message#: terminate!~n",
                            [self(), N])
    end.

Explanation

Seq = lists:seq(1, N) and Messages = lists:reverse(lists:seq(0, M-1)), These create lists of integers from 1 - N and then from M-1 - 0 (because of the reverse). These lists are used for creating processes and the messages that will be passed to those processes respectively.

LastP = lists:foldl(... This is an accumulator function. The self() value passed to the function is passed in as Pid on the first iteration, but subsequent iterations get the value computed in the method with the spawn function. The final spawn Pid is returned from the accumulator and is stored in LastP. The spawn function is setting up new processes which are running the loop function with the given values.

spawn(fun() -> [ LastP ! R || R <- Messages ] end). This is a list comprehension in Erlang. It basically takes each value from the Messages list and sends that as a message to the LastP pid which is the beginning of the Ring.

loop(N, NextPid) -> This is the function that is being run as the processes of each of the elements of the ring.

R when R > 0 -> When this process receives a message where the message is an integer greater than zero then write some info to the console and forward the message to the NextPid and use tail recursion to start waiting for another message.

R when R =:= 0 -> When this process receives a message where the message is an integer that equals zero then write some info to the console and forward the message to the NextPid, but allow this process to complete naturally.