threads (fwd)

Richard A. O'Keefe ok at atlas.otago.ac.nz
Mon Feb 18 01:42:21 UTC 2002


I wrote:
	> that one of the design rules in Erlang is
	>     HIDE THE PROTOCOL!
	> Typically, a process->process communication protocol is hidden inside
	> a module.  From the outside, you call functions to get things done,
	> and never see the protocol.  In effect, each class _does_ have its own
	> syntax, but that doesn't matter because other classes never see it.
Bijan Parsia <bparsia at email.unc.edu> replied:

	That's not quite right, is it?  I mean let's say I have two
	processes, one instantiated from module A (call it A1) and the
	other from module B (B1) (following the Erlang book's mapping of
	classes to modules and instances to processes, roughly).

	So, I want A1 to send messages to B1.  A1 has to know the right
	protocol for B1.  If I want B1 to send back to A1, B1 has to
	know A1's protocol.

No.  If A1 wants to send messages to B1, it should NOT use a direct message
send.  It should call one of the functions that module B has exported for
this purpose.  Code in module A has to know which _functions_ (public
protocol, I suppose you could say) to call, but does not and should not
know what _messages_ actually get sent (private protocol, "wire" protocol).

For a somewhat strained analogy, consider MPI.  This year, I expect to
co-supervise a Biochemistry student who is going to crunch _amazing_
amounts of data, using a network of 30-40 machines.  I think I've talked
him out of using Java (why Java? because Java supports distributed programming,
right? never mind the speed...) and into using MPI.  Now to use MPI, he'll
have to use

	MPI_Init(&argc, &argv);
	MPI_Comm_size(MPI_COMM_WORLD, &size); /* how many processes? */
	MPI_Comm_rank(MPI_COMM_WORLD, &rank); /* which am I? */
	...
	    MPI_Send(&message[0], message_n_elems, MPI_DOUBLE,
			other_proc, tag_wanted, MPI_COMM_WORLD);
	...
	    MPI_Recv(&message[0], message_max_elems, MPI_DOUBLE,
			other_proc, tag_wanted, MPI_COMM_WORLD);
	...
	MPI_Finalize();

Now, in a network of workstations, this will map to SOME sequence of
TCP messages.  Thing is, we don't need to know anything about the wire
protocol MPI uses.  Indeed, different versions of MPI probably use
different wire protocols, and of course supercomputer versions of MPI
don't use TCP at all but use some host-specific message passing scheme.
What we need to know is the functions *exported* by the MPI implementation.

One of the key concepts for Erlang is "hot loading"; it should be possible
to replace a module without restarting the program.  Thanks to the SASL
machinery, it's possible to shut down a module, replace it, and start it
up again, without clients noticing.  (Provided they send messages to
processes by name rather than by process identity.)  This can, and HAS,
involved changing the internal protocol.

	This is pretty much, I take it, what you're saying, except you seem to be
	saying that you can *hide* the invented sytnaxes behind the standard
	sequential syntax. I'm not sure *that's* true.
	
No, I'm saying that the recommended way to design Erlang systems is to
hide the message passing inside modules.  The "invented syntax" here is
the private protocol (set of messages understood by the processes that are
managed by the module).   The "standard sequential syntax" is function calls
(public protocol).

The rule of thumb is that a "naked" message send should normally be done
only to a process that is managed by the module where the send lexically
appears.

One way in which this is relevant to Smalltalk is the distinction between
"public protocol" -- interfaces you can use from outside a module and which
are meant to be fairly stable -- and "private protocol" -- stuff you can
do inside a module which can change completely from version to version.

This is one thing that could improve in Squeak browsing.
There's no way to tell, for example, which of the N hundred methods in
Morph are meant for ordinary mortals to use and which of them are there
to support other things and could be expected to change or disappear.

	> so it's expected to fail from
	> time to time, and you __really__ don't want to mess around with the details
	> of a protocol that's designed to handle that.
	
	I'm lost. I thought the issue was having to design (and later read) the
	syntax of messages. Transport level issues seem a slightly different
	thing.
	
I thought the issue was the usefulness of _hiding_ protocols.

What do I mean by a protocol here?
In Erlang, when you do
	Pid ! Message

Pid should be
    - a process ID (a primitive data type) for a process on this or some
      other machine
    - a process name (a Symbol) which is looked up in the registry
    - a reference to a process name on some other machine
If the Pid refers to a dead or unreachable process, or if there is no
process currently registered by that name, the message is silently discarded.

The Message can be any data value.  Erlang data values are rather like Scheme
data values, except for being immutable.  Symbols, numbers, characters,
cons cells, vectors, even closures nowadays, process ids, unique labels, ...

A "protocol" in the sense that I am using is (to start with) a data type
which specifies a set of messages that the receiver is interested in receiving.

If you send a message that is not in the receiver's protocol, it might
hang around forever, it might be purged if the garbage collector decides
that the receiver's mailbox could do with emptying, or it might be discarded
fairly promptly if the receiver is carefully coded.  A receiver chooses what
messages it receives when using pattern matching.

More generally, a "protocol" describes what should happen in response to
each different form of message.

	(Of course, when you manually encode the PID, etc.  you *are*
	layering a transport (request/reply) on top of the ansych
	one...that seems to be a bit of a lack in Erlang, rather the way
	Prolog limits you to one builtin search strategy, whereas
	Mozart/Oz let's you specify different ones without having to
	code them up.)

The Prolog "limitation" lets you use forward chaining, backward chaining,
top down parsing, bottom up parsing, left corner parsing, depth first
search, breadth first search, iterative deepening, &c &c.  It's an
adequate building block for _programming_ these things, and many others.
Mozart/Oz may let you specify SOME different strategies, but whatever set
they provide, there will be others that you DO have to code up.  (There
was even an implementation of coroutining in DEC-10 Prolog, done entirely
in Prolog.)

In the same way, Erlang provides a minimal _building block_.  There's
no automatic reply in UDP, after all.  The designer was familiar with
a wide range of message passing architectures and specification languages
and had built telecoms applications in several well-known and several
proprietary languages.

Here's an example of why an automatic response is not a good thing in a
primitive.  TCP.  If you send an ACK for every packet you receive,
performance suffers.  So some TCPs, after startup, switch to sending
an ACK for every second packet they receive, &c.

If function call is cheap, and basic message passing is cheap, it doesn't
_matter_ if other things _have_ to be coded up on top, what matters is that
they _can_ be coded up affordably.  And then you stick them in the library
and presto chango, a technique that nobody _else_ has to code up, but
without building overheads into the primitive mechanism.

It's rather like the way Smalltalk doesn't build in multiple inheritance,
but didn't stop people experimenting with it because it _does_ provide the
hooks you need to do it yourself.




More information about the Squeak-dev mailing list