.@ Tony Finch – blog

You can classify formalisms for message-passing concurrency into channel-oriented (like the π calculus) or destination-oriented (like the actor model).

The E programming language is based on a kind of remote procedure call, and so is superficially destination-based: the communication described by the RPC is targeted at a particular destination object. However the process performing the RPC does not block waiting for the result, since this can be a very long time in widely distributed systems. Instead it immediately gets a promise for the eventual result; it can then perform RPCs on the promise which will be handled by the evential result as soon as it has been determined. This allows you to pipeline RPCs so that networking delays overlap instead of accumulate.

E also allows you to create promises without making an RPC, and these promises are directly equivalent to a one-shot channel. You can pass the sending and receiving ends around in any way you like, and the receivers can attach actions to the promise to be performed when it has been fulfilled. On this foundation it’s reasonably straight-forward to create persistent channels: whenever a channel is used, the sender has to create a replacement promise which is passed with its main message; after handling the message, the receivers listen for the next message on the new promise instead of the old one. If there are multiple senders then they must have copies of both ends of the promise, so that they can spot when it has been used and update their reference to the replacement.

However not all promises are first class. When you perform an E-style RPC, you get an explicit handle on the receiving end of a promise, but not on the sending end - that is just implicit in the return value of the remote procedure. If this procedure returns a promise (the result of another RPC) then the two promises effectively become unified: fulfilling the second promise fulfills the first. There’s no way of having more than one sender on this chain of promises because you can’t branch it, unless you add first-class promises.

This all has some implications for distributed garbage collection: as well as direct references to remote objects, E can refer to them via promises. With first-class promises the distributed topology can be arbitrarily complicated via both kinds of references. I do not yet know whether restricting promises to RPC only is worth it for simplifying GC and/or too harmful to programming flexibility.