Skip to content

Instantly share code, notes, and snippets.

@rektide
Created July 15, 2013 09:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rektide/5998727 to your computer and use it in GitHub Desktop.
Save rektide/5998727 to your computer and use it in GitHub Desktop.
Chain of Command, the: Receiver-side Handling A Generalized First Class Model of Context Passing Processing

Of execution

Programmers often regard executing a function as the same as a method call, yet: a function is a distinct operation being called, a receivership, and a method call is the sender asking for it's associate operation, whatever that may be at the time, to be run: we may change the function on on an object's slot in a dynamic language, for example, yet the method calling will often remain the same.

Of type-based multimethod dynamic dispatch

Multimethods are a means of handling dispatch at runtime (dynamically) based upon the type of arguments. Instead of assigning a single function for a method, it assigns multiple function implementations, each guarded by a check of types: whatever we call the method with must pattern match with an available known function. Frequently no code needs to be written for multimethods aside from the function implementations: strong typing of the function definition is fed in to a runtime decision tree that does the selection whenever a method is invoked. The receiver nor the sender do any explicit decision making.

Ex: Multimethods in C# 4.0 with 'dynamic' http://blogs.msdn.com/b/laurionb/archive/2009/08/13/multimethods-in-c-4-0-with-dynamic.aspx

Of guarded dynamic dispatch

In Erlang, dynamic dispatch is done through a more general means, of two parts: functions have parameters with either fixed literals or free variables which must be pattern matched, and functions may declare guards. Instead of a type check, function execution begins with a pattern match, finding the first function that matches the free and fixed parameters of the function. Once a candidate is selected, a pre-function routine is ran called a guard: the guard returns a boolean true/false, declaring whether this function indeed does wish to handle the method call or not. If a guard fails, the runtime will continue searching for further functions which can successfully pattern match the invoked arguments, and running their guards to: until some function pattern matches and then passes it's guards, or until there are no more functions, dispatch through the available functions continues.

In Erlang we move away from a purely runtime constrained multiple dispatch option: in the first we have a check of types that determines success, in Erlang we now have preliminary pattern matching a guard which is authored alongside the function. We now have the receivers declaring for themselves what may happen.

The Chain

The Chain of Command Pattern (also called the Chain of Responsibility) is yet another form of dynamic dispatch. Often implemented as a library, a chain of command is an explicit sequence of processes (or processing-objects or functions or handlers, &c) which will attempt to further the resolution of the initial invocation. The rules of the chain of command are loose; it simply states that one process is handed the invocation first, and it attempts to work as much processing as it can, and either completion is signalled from the first handler, or the job is continued by the next handler, repating on until completion is arrived or process-abort is signalled.

Chain: Node's most favorite pattern

A common example of the Chain of Command pattern- although rarely identified by their Pattern name, the Chain of Command or Chain of Responsibility pattern- are the popular Connect and Express modules for composing Node.js servers: http.createServer(connect().use(logger).use(favicon).use(staticAssets).use(sessionFilter).use(loginFilter).use(app))

Here we see clearly each responsibility handled in a chained order, one after another: all requests will make it through a logger, through a check for favicons and staticAssets, and perhaps on through two filters before making it to the application itself.

Continuation Passing through a Chain

A Chain of Command is inherently an implementation of a continuation passing system: it's an technique for and abstraction of the control flow of a program. Connect and Express, behind the scenes, maintain a state-tracking system following a request through each layer of it's chain as it moves towards completion. The continuation is passed down the chain.

CPS

Please note that the term Continuation-Passing-Style is heavily associated with passing a function into methods to allow the method to signal it's completion: this is the Node style callback:

function myHandler(err,done){
  if(!err) doWork()
  done()
}

Your author finds this mercilessly boring and besides the whole point. This is about passing of control-flow through the stack, but control flow remains free of context aside from that which is passed around manually.

Context: data yes, flow control maybe.

In common Chain implementations, execution of the chain hinges upon a malleable consistent Context object which is passed through the chain (such as http://commons.apache.org/proper/commons-chain/apidocs/index.html#line.115). As Context flows through the Chain, processes build and establish more Context about the processing they have done.

Connect and Express work similarly, except instead of a core Context object these two chain of command systems relay distinct Request and Response context objects down the chain. Yet we can see these quite clearly as Context, if we consider the example of the session extension, which gloms a persistent session property onto Request. The session extension is building the Request Context, adding to it and sharing that new context with modules run latter down the line.

The only twist to make Context come full circle, to make it a passed Continuation (describing the flow control through the chain) is for the Context to capture it's position in the Chain walking.

If we embed the executing chain in our Context and make it mutable, we can do more than talk about the flow control that has happened (capture the Continuation, the distance): we can have our process-objects engineer the future flow control that the Context will undergo.

Receivership, comparing versus classical, language-driven dynamic

We began discussing classic multimethods, and Erlang's guarded multimethods: both examples of defining methods up front that a dynamic language runtime can use to decide the course of computing. A chain of command is a more general implementation of dynamic dispatch, and is characterized not as a language-driven dispatch, but as a code-driven dispatch: potential handlers are run in sequence and are encouraged to contribute computation as they are capable. Instead of language-defined behavior, dispatch is a more general code-defined behavior.

Of liveliest processes - a bundle of processing, and the lattice of computing

Session extension runs asynchronously- it may involve going to the database in an asychronous way- but as it is implemented the Chain is blocked until it returns: the chain ticks one step at a time. Once the chain subprocess runs, the following subprocess expect the session subprocess to have completed.

Yet we have non-blocking constructs for designating and demarcating in-progress computation: rather than provide a final and complete session property, the session extension could provide a sesssion promise or continuable to the context and immediately signal the next subprocess. Put another way, the session subprocess begins, forks a new child sub-sub-process, puts the deferred for that sub-sub-process into the context, and then terminates it's own execution, thereby (having not signalled completion of the context,) handing control to the next subprocess. The next process might do likewise: attach it's own new sub-sub-process on to the end of the session's sub-sub-processes- the deferred for it's final valuation- leave it's own deferred for that child sub-sub-process dependent upon session's child sub-sub-process, and then pass control to the next subprocess in the chain.

In such a way, the Context becomes a concensus, a system of record for interoperating subprocesses to contribute and coordinate their and their sub-sub- and sub-sub-sub-&c processes views of the overall computation. The context proceding through it's chain is the bundle of computing; individual subprocesses, and a record of values provided by the subprocesses, which other subprocesses might come to use.

Using undeclared values, implicit deferred

In the above model each subprocess relies on a value or a deferred to already exist: any subprocess needing a session context needs to be staged to execute after the session subprocess. These dependenant subprocesses can stage their own children processes so long as they have a value, or know they'll have a value in the future.

But the context need not capture only values and outstanding, in-progress values: the context is a system of registry, and certainly it could register an interest in a value no one yet knows how to provide. Asking for a value, the Context can shield the asker, if it wants, from having to know whether the value is available, whether it's in progress and thus there is a promise for it already, or whether the context will create a deferred value implicitly for the undeclared value.

Declaring providers

Similarly a Context might maintain a list of processing-objects outside of the core process chain that describe their potential outputs values (and perhaps as well their needed, helpful or useable inputs too): asking for an undeclared value would trigger the Context's search for a provider capable of producing the desired value.

Lattice, ho

The above mechanistics aggregate to a body very close in nature to a modern Inversion of Control container. IoC is traditionally value oriented, whereas the chain of command codes a procedural work-engine, again remaining general purpose and resistant to needing a completely defined runtime model. As dynamic dispatch in a language requires well defined behavior and leaving only some room for code to interface with the autonomic decision of the language, so too IoC is usually seen providing an autonomic fulfillment based off of a well reasoned graph of facts the IoC has been preconditioned with.

Yet we have a far more capable model of computing at hand here: we have a series of process-objects (session, and those handlers following it), and a shared context that these processes get passed (hopefully one and the same, satisfied when the context includes and makes manipulablthe chain and chain-cursor embedded and manipulable).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment