[erlang-questions] Ideas for a new Erlang
Sven-Olof Nystr|m
svenolof@REDACTED
Tue Jul 22 11:38:09 CEST 2008
Ulf Wiger writes:
> 2008/6/30 Sven-Olof Nystr|m <svenolof@REDACTED>:
> >
> > Since I came up with the channel concept, I've been looking for some
> > convincing example of an Erlang program that could be implemented
> > using selective receive, but was not possible to implement using
> > channels (or where the solution with channels was more complex). When
> > I saw your buffer example, I thought for a moment that it was the
> > example I was looking for.
>
> A good place to start might be the POTS control program, which was
> presented in the Erlang book, and also used (in a slightly different form)
> in the Introductory Erlang course.
>
> I used it in my "Structured Network Programming" presentation to
> show the consequences of different programming styles in multiway
> signaling:
>
> http://www.erlang.se/euc/05/1500Wiger.ppt
>
> I'm not convinced that it will be sufficient to reveal significant differences
> between the standard erlang style and channels, but it's a good place to
> start, and the code can be expanded in a number of interesting ways.
>
> BR,
> Ulf W
>
> (There's a working simulator to go with the program, but the OTP team
> would have to give permission to release it in public. I'm sure you can
> get your hands on it anyway.)
Sorry about disappearing from the discussion for so long.
I took a look at the POTS program in the Erlang book. It is actually
easy to rewrite it to use neither channels nor selective receive.
I also looked at the gen-server module.
Detailed comments follow. (A non-selective receive is one that always
pick the first message in the mailbox.)
Sven-Olof
The book's implementation of POTS only contains four receive
expressions. Among these, only one (in the function make_call_to_B/3)
is selective.
In the book's example, each telephone has one process that is
responsible for the interaction with that telephone. Each process can
receive five types of messages:
- off_hook
- on_hook
- {digit, Digit}
- {seize, Pid}
- cleared
The first three are of course due to the user's actions. The third
handles the case when another process attempts to connect to the
current process. When this happens, there are two possible responses:
- seized
- rejected
So if process X tries to connect to process Y, X sends {seize, X} to
Y, and Y responds either with a message containing the atom 'seized',
if the connect was successful, or 'rejected' if it was not. Now, there
is no way to distinguish a response from one processes from another,
so the implementation is correct only if messages cannot be delayed
for too long.
It is easy enough to make the receive expression in
make_call_to_B non-selective by adding two clauses:
on_hook ->
B_Pid ! cleared,
idle(Address);
Other ->
make_call_to_B(Address, B_Pid, B_Address)
However, neither this solution nor the book's solution seem
completely satisfactory. Using selective receive, a better solution is
to tag responses with the sending process. Using channels, a better
solution is to create separate channels for responses.
I have not attempted to re-write the gen-server, but I have taken a
close look. Below, numbers in parentheses indicate line numbers.
The gen-server (of stdlib-1.14.5) contains 14 receive expressions:
- The one in the main loop (305) is non-selective.
- several are used in the monitoring of processes (366, 508) and
nodes (420, 425, 435, 473, 477).
These cannot immediately be rewritten using channels. However, if
we follow the principle that the mailbox of a process should only
contain messages related to the service that the process provides
(i.e., not to messages part of the implementation of the service)
the messages indicating that a process (or node) is down should
arrive in a separate channel. Thus, the New Erlang versions of
erlang:monitor (and possibly also erlang:demonitor) needs an
additional argument giving the channel where the messages are to be
sent. With the additional argument, it is possible to rewrite these
receives to use channels.
Also, note that several of these are very simple, for example
unmonitor(Ref) when is_reference(Ref) ->
erlang:demonitor(Ref),
receive
{'DOWN', Ref, _, _, _} ->
true
after 0 ->
true
end.
Here, the purpose is simply to cleanup the mailbox. If we use a
separate channel to collect monitoring messages, we can simply drop
the channel without examining its contents
- One receive (352), used in the implementation of multi-call, either
takes a message from the calling process or a notification that it
is down.
- Similar to the previous case, and also in the implementation of
multi-call, several receives (401, 415, 456, 469) match either a
reply from a node, or a message (due to monitoring) that the node
(or the processes executing on the node) is down.
Multi-call collects responses from a number of nodes. The nodes are
given in a list but the messages from the nodes will of course
arrive in an arbitrary order. Here, selective receive is used to
match the messages in the order given in the list of nodes. A
solution that does not use selective receive will be slightly more
complex (the simplest solution would require a function that
removes a node from a list of nodes). The current solution has
quadratic complexity in the number of nodes.
The implementation of multi-call is surprisingly complex. Ten of
the fourteen receive expressions of the gen-server module are used
in the implementation of multi-call.
- One receive (444) removes a message sent by a timer. To rewrite
this receive using channels one needs to use a separate channel for
the timeout message. As the destination of the timeout message is
given explicitly in the call to the built-in function
erlang:start_timer/3, a rewrite is straight-forward.
More information about the erlang-questions
mailing list