I am new to functional programming and just switched from haskell (Didn't like it much) to erlang (quite fond of it). As I am learning as an autodidact, I stumbled over these Exercises and started doing them.
I came as far as this problem:
- Write a function which starts 2 processes, and sends a message M times forewards and backwards between them. After the messages have been sent the processes should terminate gracefully.
I resolved it like this and it works (maybe it can be done better; any comment highly appreciated):
-module (concur).
-export ( [pingpong/1, pingpong/2] ).
pingpong (Msg, TTL) ->
A = spawn (concur, pingpong, ["Alice"] ),
B = spawn (concur, pingpong, ["Bob"] ),
B ! {A, TTL * 2, Msg}.
pingpong (Name) ->
receive
{From, 1, Msg} ->
io:format ("~s received ~p and dying.~n", [Name, Msg] ),
exit (From);
{From, TTL, Msg} ->
io:format ("~s received ~p.~n", [Name, Msg] ),
From ! {self (), TTL - 1, Msg},
pingpong (Name)
end.
The real problem is the next exercise:
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.
As I am not sending the message back to its originator, but to the next node in the chain, I somehow have to pass to the sending process the process of the recipient. So I imagined that the function would look something like this:
pingCircle (Name, Next) ->
...
receive {TTL, Msg} -> Next ! {TTL - 1, Msg}
...
But how do I start this whole thing. When I spawn the first function in the circle, I still haven't spawned the next node and hence I cannot pass it as an argument. So my naive approach doesn't work:
First = spawn (concur, pingCirle, ["Alice", Second] ),
Second = spawn (concur, pingCirle, ["Bob", Third] ),
...
Also the approach of passing the spawn call of the next node recursively as a parameter to it predecessor, doesn't solve the problem how to close the circle, i.e. passing the last node to the first.
The question is: How can I build this circle?
EDIT:
Thanks to your great answers, I managed to what I intended. Hence this question is solved.
One possible solution is:
-module (concur).
-export ( [pingCircle/3, pingCircle/2] ).
pingCircle (Names, Message, TTL) ->
Processes = lists:map (fun (Name) -> spawn (?MODULE, pingCircle, [Name, nobody] ) end, Names),
ProcessPairs = lists:zip (Processes, rot1 (Processes) ),
lists:map (fun ( {Process, Recipient} ) -> Process ! {setRecipient, Recipient} end, ProcessPairs),
Circle = lists:map (fun ( {Process, _} ) -> Process end, ProcessPairs),
hd (Circle) ! {Message, TTL - 1, lists:last (Circle) }.
rot1 ( [] ) -> [];
rot1 ( [Head | Tail] ) -> Tail ++ [Head].
pingCircle (Name, Recipient) ->
receive
{setRecipient, NewRecipient} ->
pingCircle (Name, NewRecipient);
{Message, 0, Originator} ->
io:format ("~s received ~p with TTL 0 and dying.~n", [Name, Message] ),
if
Originator == self () -> io:format ("All dead.~n");
true -> Recipient ! {Message, 0, Originator}
en开发者_如何学编程d;
{Message, TTL, Originator} ->
io:format ("~s received ~p with TTL ~p.~n", [Name, Message, TTL] ),
if
Originator == self () -> Recipient ! {Message, TTL - 1, Originator};
true -> Recipient ! {Message, TTL, Originator}
end,
pingCircle (Name, Recipient)
end.
Here is my peer review link.
This exercise has become a rite of passage for all erlang programmers. I gave a working solution to it here, along with an explanation that may be helpful.
Spawn them first, then send them a start signal.
The start signal would be sent after all the processes are already running.
Someone already came up with the answer here -> http://simplehappy.iteye.com/?show_full=true
My answer.
-module(con_test).
start_ring(Msg, M, N) ->
[First|_]=Processes=[spawn(?MODULE, ring, []) || _ <- lists:seq(1,N)],
First ! {rotLeft(Processes), {Msg, M*N}}.
ring() ->
receive
{_List, {Msg, Count}} when Count == 0 ->
io:format("~p got ~s. Enough! I'm out.~n", [self(), Msg]),
exit(normal);
{[Next|_] = List, {Msg, Count}} when Count > 0 ->
io:format("~p got ~s. Passing it forward to ~p.~n", [self(), Msg, Next]),
Next ! {rotLeft(List), {Msg, Count-1}},
ring()
after 1000 ->
io:format("~p is out.~n", [self()]),
exit(normal)
end.
rotLeft([]) -> [];
rotLeft([H|T]) -> T ++[H].
精彩评论