[erlang-questions] Message Receive Semantics (was eep:MultiplePatterns)

David Mercer <>
Tue Jun 3 22:25:00 CEST 2008


Jay Nelson wrote:

> So here is the concrete challenge:

> 

> 1) Send a message to a linked process:

>      a) {request, Msg}

> 2) The other process should generate 20 or so messages randomly

>      and then quit

> 3) Always handle messages in the following order:

>      a) 5 messages of {reply, high, Msg}

>      b) 3 messages of {reply, normal, Msg}

>      c) 1 message of {reply, low, Msg}

>      d) 'EXIT' message

> 

> Of course, there may not be 5 remaining high messages or 3 remaining

> normal messages so you need to deal with these end cases.  In doing so,

> make sure you don't just end up polling every 100 milliseconds.  I've

> simplified the problem by only having one process that can generate

> replies, so that the selective receive doesn't become even more

> complicated.

> 

> I challenge any beginner or intermediate erlang programmer to write

> and post the code for both processes and  a description of your results.

> All the advanced erlang people can help point out the issues that will

> have to be addressed when you change the initial code approach.

> 

> At least, that is my bet.  Valentin may be right.  Let's see some code!

 

OK, let me begin with the output from my program so you can see if I got the
answer functionally correct:

 

2> nelson:start("Message text").

Sending message 20 with high priority.

Sending message 19 with high priority.

Sending message 18 with normal priority.

Sending message 17 with normal priority.

Sending message 16 with high priority.

Sending message 15 with high priority.

Sending message 14 with high priority.

Sending message 13 with high priority.

Sending message 12 with low priority.

Sending message 11 with normal priority.

Sending message 10 with normal priority.

Sending message 9 with low priority.

Sending message 8 with high priority.

Sending message 7 with low priority.

Sending message 6 with normal priority.

Sending message 5 with low priority.

Sending message 4 with low priority.

Sending message 3 with normal priority.

Sending message 2 with normal priority.

Sending message 1 with high priority.

Exit (normal) signal received from sender; begin prioritized receive
sequence.

Received {"Message text",high,20}.

Received {"Message text",high,19}.

Received {"Message text",high,16}.

Received {"Message text",high,15}.

Received {"Message text",high,14}.

Received {"Message text",normal,18}.

Received {"Message text",normal,17}.

Received {"Message text",normal,11}.

Received {"Message text",low,12}.

Received {"Message text",high,13}.

Received {"Message text",high,8}.

Received {"Message text",high,1}.

Received {"Message text",normal,10}.

Received {"Message text",normal,6}.

Received {"Message text",normal,3}.

Received {"Message text",low,9}.

Received {"Message text",normal,2}.

Received {"Message text",low,7}.

Received {"Message text",low,5}.

Received {"Message text",low,4}.

complete

 

It appears to me to do as requested.  It receives 5 high priority messages
before receiving 3 normal priority messages and then a single low priority
message.  When there are no more of any particular type, it skips it and
moves to the next.  Here's my code:

 

%%% @author David Mercer <>

%%% @doc Programming challenge by Jay Nelson, 6/1/08.

%%%

%%% Jay Nelson wrote:

%%% <pre>

%%% > So here is the concrete challenge:

%%% > 

%%% > 1) Send a message to a linked process:

%%% >      a) {request, Msg}

%%% > 2) The other process should generate 20 or so messages randomly

%%% >      and then quit

%%% > 3) Always handle messages in the following order:

%%% >      a) 5 messages of {reply, high, Msg}

%%% >      b) 3 messages of {reply, normal, Msg}

%%% >      c) 1 message of {reply, low, Msg}

%%% >      d) 'EXIT' message

%%% > 

%%% > Of course, there may not be 5 remaining high messages or 3 remaining

%%% > normal messages so you need to deal with these end cases.  In doing
so,

%%% > make sure you don't just end up polling every 100 milliseconds.  I've

%%% > simplified the problem by only having one process that can generate

%%% > replies, so that the selective receive doesn't become even more

%%% > complicated.

%%% > 

%%% > I challenge any beginner or intermediate erlang programmer to write

%%% > and post the code for both processes and  a description of your
results.

%%% > All the advanced erlang people can help point out the issues that will

%%% > have to be addressed when you change the initial code approach.

%%% </pre>

 

-module(nelson).

 

%% API

-export([start/1, start/3]).

 

 

%% @spec start(Msg::any()) -> complete

%%

%% @doc Execute the Nelson challenge.

%%

%% Spawns a linked process, waits for the process to exit, then receives the

%% messages in accordance with the priority rules laid out by Nelson.

%%

%% The Msg argument is simply passed through to the spawned process, which

%% will return it in the messages it sends back.

%%

%% @equiv start(Msg, 20, {5, 3, 1})

 

start(Msg) -> start(Msg, 20, {5, 3, 1}).

 

 

%% @spec start(Msg::any(), N::integer(), PriorityRules::PriorityRules) ->
complete

%%     PriorityRules = {High::integer(), Med::integer(), Low::integer()}

%%

%% @doc Execute the Nelson challenge.

%%

%% Spawns a linked process, waits for the process to exit, then receives the

%% messages in accordance with the priority rules laid out by Nelson.

%%

%% The Msg argument is simply passed through to the spawned process, which

%% will return it in the messages it sends back.

%%

%% N is the number of messages for the spawned process to send.

%%

%% The PriorityRules specify the numbers of messages of each priority which

%% are received in each cycle.  Nelson specified that these numbers be 5, 3,

%% and 1 respectively.

 

start(Msg, N, {High, Med, Low}) ->

 

       % Create linked process for sending prioritized messages

       

       Self = self(),

       process_flag(trap_exit, true),

       Pid = spawn_link(fun() -> start_sending(Self, Msg, N) end),

       

       % Send the initial message

       

       Pid ! {request, Msg},

       

       % Wait for the linked process to exit

       

       receive

              {'EXIT', Pid, Reason} ->

                     % Generally we expect Reason = normal, but we don't
care if it's something else

                     

                     io:format("Exit (~p) signal received from sender; begin
prioritized receive sequence.\n", [Reason]),

 

                     % Once we receive the exit message, begin receiving the
messages

 

                     loop({0, High}, {0, Med}, {0, Low})

       end.

 

 

start_sending(Pid, Msg, N) ->

       

       % Seed the random number generator

       

       {A1, A2, A3} = now(),

       random:seed(A1, A2, A3),

       

       % Send

       

       send(Pid, Msg, N).

 

 

send(_Pid, _Msg, 0) -> complete;

send(Pid, Msg, N) when N > 0 ->

       Priority = case random:uniform(3) of

                     1 -> high;

                     2 -> normal;

                     3 -> low

              end,

       io:format("Sending message ~p with ~p priority.\n", [N, Priority]),

       Pid ! {reply, Priority, {Msg, Priority, N}},

       send(Pid, Msg, N - 1).

 

 

%% loop/3 is the prioritized receive loop.  It works by keeping track of

%% counters for the three priorities, counting up each one in priority

%% order until it reaches the max.  If no message of that priority is

%% received, then the max is set to 0.  When all counters are zero, all
messages have been received.

 

loop({0, 0}, {0, 0}, {0, 0}) -> complete;

loop({H, H}, {M, M}, {L, L}) -> loop({0, H}, {0, M}, {0, L});

 

loop({H1, H2}, {0, M}, {0, L}) when H2 > H1 ->

       receive

              {reply, high, Msg} ->

                     io:format("Received ~p.\n", [Msg]),

                     loop({H1 + 1, H2}, {0, M}, {0, L})

              after 0 ->

                     loop({0, 0}, {0, M}, {0, L})

       end;

 

loop({H, H}, {M1, M2}, {0, L}) when M2 > M1 ->

       receive

              {reply, normal, Msg} ->

                     io:format("Received ~p.\n", [Msg]),

                     loop({H, H}, {M1 + 1, M2}, {0, L})

              after 0 ->

                     loop({H, H}, {0, 0}, {0, L})

       end;

 

loop({H, H}, {M, M}, {L1, L2}) when L2 > L1 ->

       receive

              {reply, low, Msg} ->

                     io:format("Received ~p.\n", [Msg]),

                     loop({H, H}, {M, M}, {L1 + 1, L2})

              after 0 ->

                     loop({H, H}, {M, M}, {0, 0})

       end.

 

Please advise.  Thank-you.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20080603/21bb8686/attachment.html>


More information about the erlang-questions mailing list