Before I start, one peculiarity I didn’t mention in the previous post is garbage collecting methods. Method lookup tables are kept as properties of the “type” objects with which they are defined. This means that if the object gets collected, so does the method table, which is good. Less good though, is that if the method has other arguments: they will retain an entry in their method tables, even though that method can never be invoked.
For that reason it is important to restrict method definitions to long-lived objects; in general this is fine since it’s the objects representing types that you care about – in other words, those that are supposed to hang around, so other objects can be based on them.
Anyway, yes hello. Examples of multiple dispatch.
This first one is for decoding JSON values. The scheme is pretty easy
to figure out: a ‘!’ property in the JSON value is a kind of reader
syntax, giving a type name.
decodeValue just determines whether the
value uses this special encoding or not. Then there’s two procedures:
one with methods specialising on the kind of “normal” object, since
'object'; and, another which has methods
specialising on the type name of an encoded object.
This could all be done with a single function, of course. However, this way I can add a special type elsewhere in the code, which would otherwise require some kind of registration mechanism.
This example is a procedure for making a widget (something that will
be rendered into the web page) given a value of the kind decoded in
the example above.
render is a procedure created elsewhere that has
methods to render specific widgets to DOM nodes. The idea is that a
value will be
widgetized, then the result is
There is some tricksiness in the interplay between these two procedures.
For the sake of not defining more types, primitives (that is strings,
numbers etc.) are their own widgets. We want to define
Object later on, so I have to define methods for the primitive types
(To be honest it’s a bit of a pain that
Object is both a common type
of value and the supertype of almost everything. In any case, note
that using multimethods has the effect of flattening out what might
otherwise be a nested if-then-else statement – imagine if these
methods all had their own specific implementations, and there were
three arguments rather than two.)
Then, so that those primitive values can act as widgets, at line 30
render procedure is given a default method that will simply wrap
the stringified value in an HTML element. At line 40 this is
specialised for strings, to put double quote marks around them.
In line 17,
widgetize is given a method that effectively resends
invocations with one argument to invocations with two arguments,
saving extra definitions.
You may have noticed that all the
render methods expect a function
as the second argument (it’s supplied with a function to output DOM
nodes), so the only argument they specialise on is the first; and,
given that fact, why don’t I just use a regular single-dispatch
One reason is that I can specialise on things other than objects;
e.g., literal strings, as in
decodeSpecial above. Another is that
the multimethods are values and as such I can pass them around,
e.g., as arguments to a function (although you can always construct a
function that will invoke the appropriate property, I suppose). Also
since the multimethods are values, there’s no need to assign a
property of a global object (say
String) if I want some new
polymorphic procedure (say
Again, this opens up the possibility of adding kinds of widget elsewhere. And in fact, in another file, I have these:
These are special values that aren’t from encoded JSON;
example, is just a placeholder value (it’s rendered to one of those
AJAX spinners you have now).
I ought to note that most of the code doesn’t use multiple dispatch. Quite a lot of it uses regular old single dispatch. The main uses of multiple dispatch are to
- allow code to be extended after the original definition
- avoid adding properties to top-level objects e.g.,
- flatten complicated dispatch into a more tabular form