DAVE'S LIFE ON HOLD

Message Passing Architectures Continued

Once th idea of routing messages as the basis of application flow control sinks in, a wide range of new possibilities open up. Think of a hub and spokes topology. A central hub like on a bicycle tire, is connected to a radial fanout of message chains. When one actor sends a message to the hub, it starts multiple parallel cascades of actions which transform the state of the aggregate program.

For example, consider the case where one wishes to have an arbitrary number of images on a selection screen, and each image plays an animation when the user moves towards it. Let's say we want the speed of the animation to also be a inverse squared law style behavior. Using the hub and spoke model, a Mouse object can send Move events to the hub. The hub then relays these events to each image, which determines it's distance from the event and animates itself accordingly.

Let's now say we want each of the animations to not have to keep track of time on their own, to avoid synchronization problems with out hardware. We can have the Timer object relay to the hub a Time event and each of the animations can advance themselves one frame. Additionally, we might want them each to stop playing, fade out, and disappear when we click on one of the images. The Mouse can either send a message directly to the hub, or may send a Click message directly to the object under it. Personally I prefer the hub as it is a cleaner design for this problem, but have to admit I love the immediacy of the mouse sending messages directly to it's target object. In this case, each Click would have to trigger a message send to the central hub, which starts the message chain for each object to stop, fade, and disappear.

Message passing can also be designed to seamlessly trigger additional behaviors. For example, if one of the consumers of the hub is a Wire object which carries messages to a backend Switch, we can also forward those messages to backend processes that operate the same way our frontend contexts do. This allows us to make parallel work which is natural to do remotely. Updating databases, transforming large data sets, logging behavior, and brokering access controls are all best served by a combination of local (progress indicators) and remote (actual work).

The key to making this work is understanding how to decompose your problem into a collection of atomic behaviors. Rather than focus on data munging being atomic, we carry that notion up the stack, and make each behavior atomic. In effect a message send is an opportunity to interrupt the behavior of an object. Whenever a message is sent out, there is a period of propagation delay which is the switching cost for the change of context. That change can be on a single CPU, between two CPUs, or between two networks. This delay, however, is an opportunity to synchronize two processes without additional overhead. Those familiar with the callback style of event driven programming in a web browser will see this as a natural extension. Instead of blocking threads on mutexs, we simply defer control to another object, and wait for them to hand control back. It becomes imperative in this sort of system that each object maintains it's own internal state in a consistent manner. Shared data becomes an anathema in this scenario, and this also in effect also extends to shared behavior.

If you are classically trained in classical OO, the idea of an object changing class is probably scary. While Smalltalk has a become: method that converts an object to any other class of the same shape, it does not allow for any sort of radical metamorphosis from caterpillar to butterfly. But it is exactly this sort of behavior a Message Passing Architecture needs. Objects which can dynamically alter their external interface based on internal state allow you to selectively ignore or accept messages based on an object's current state. You can think of the list of messages an object reponds to as its class (or Kind). By changing what kind of object each object is, you can avoid checking for invalid message sends and also express state through your interface. This is similar to how the chemical signature of a cell's membrane changes in responses to changes to allow or prevent different chemicals to pass through. This allows us to not code defensively, because we are in no danger of processing messages that are inappropriate to our object's current state.

In a language like JavaScript, we can mimic this behavior by adding or removing behavior to the current object. We must be careful though not to have any shadow methods in out inheritance tree that we would expose by removing a local override. This is a large part of the reason I tend to favor copy construction over inheritance. It allows us to treat each instance as a fully malleable and unique entity, complete with it's own history and genealogy. Mixing in code by copying methods (and rebinding to the new context) prevents sharing state unintentionally with other objects. Mutation of a prototype chain becomes a danger of the past. It does mean however that we have much more opportunity for idiosyncratic behavior. Debugging requires reasoning about individual instances and not categorical reasoning.