In Common Lisp, a generic function defines an abstract operation and a parameter list. In Clojure, a multimethod takes a similar role:
The multimethod's name is multi, and :shape is a dispatch function used to select the actual concrete implementation. (Remember that keywords like :shape are also lookup functions.) Now, I can create one or more methods:
The first method will draw things with a :shape of :square, and the second method will draw things with a :shape of :circle:
The draw multimethod is emulating single inheritance, if you think of an object's :shape value as its type. But the multimethod mechanism is more general.
A more complete example
Let's say that I need to implement account withdrawals. Different kinds of accounts will have different rules:
Bank accounts are simple accounts. Withdrawals will work if there is enough money available.
Checking accounts attach an overdraft account which can be used to cover large withdrawals.
The multimethod for withdraw could look like this:
The bank account implementation will do a simple withdraw.
PCL uses Common Lisp's method combination to share implementation code between the different account types. Clojure's dispatch is much more general, so a general method combination mechanism is not appropriate. I am taking a different approach, pulling the shared code into a helper function raw-withdraw:
The withdrawal differs from the original PCL implementation in one other way. The original code mutated the account. Since mutation is a no-no, I am instead returning a new account object, associng in the changed balance. In the example below, I am using a let just to show that the original account is unchanged.
The checking account is a little more complex. First, I have to shuttle money in from the overdraft account (if necessary), then raw-withdraw as before:
Again, all the objects are immutable. The merge function returns a new account object (possibly with an overdraft), and the raw-withdraw returns another object:
Dispatching on more than one parameter
In languages like Java, methods are polymorphic on their first (implicit) parameter. Because multimethods dispatch on arbitrary functions, they can be polymorphic on all of their parameters.
For example, a music library might implement a beat method that is polymorphic on both the drum and the stick:
The first beat method matches only snare drum + brush, etc.:
If no methods match the dispatch value, Clojure throws an exception:
Or, you can define a :default that will match if no other dispatch value matches:
The PCL chapter demonstrates dispatch based on one or more arguments to a function, and those examples are duplicated above. There are many other things you might do with defmulti, but since they are not covered in PCL I will declare them out of scope here, and point you to some other reading:
Clojure objects have metadata, so you could dispatch based on metadata values instead of data values. See mac's post on the mailing list for an example.
Dispatch can be based on the state of an object, rather than on some kind of type tag. This lets you treat a rectangle with equal width and height as a square, even if it was created as a rectangle. See my article on dispatch in the Java.next series for an example.
Clojure's defmulti allows you to create multiple taxonomies dynamically, and trivially dispatch based on isa relationships in a taxonomy. See Rich's mailing list post introducing this feature.
We are a collection of experienced, thoughtful technologists, passionate about helping organizations deploy
technology effectively and humanely to build better futures. We like to work with you on the hard stuff.