Pure Danger Tech


Clojure multi-methods

21 Aug 2010

A friend asked me a question about multi-methods and since the response was long-ish, I’m dumping it here in case it helps someone else:

Question: “Is it possible to write a multimethod that has defmethods which handle ranges of values? For example, say that a person has an age. Can I write a multimethod that accepts a person as a parameter and returns “child” if age < 16, “adult” if 16 <= age < 66 and “senior” if age >= 66?”


Sure – how a multimethod “switches” to choose an implementation is abstracted by a function of course, specifically the dispatch function you give it when you create the multimethod. Very commonly, the dispatch function is just “class” to switch on type but it can be anything. Each defmethod specifies a specific value it matches on, so you can’t have a defmethod that directly matches a range (afaik).

In your example, you don’t really need a multimethod though – I would just use cond for that:

user=> (defn ticket [age] 
         (cond (< age 16) :child
               (>= age 66) :senior
               :else :adult))
user=> (ticket 10)
user=> (ticket 20)
user=> (ticket 90)

Now, you could use the ticket function above as the dispatch function for a multi-method if you wanted to switch behavior based on those kinds of tickets and that would make some sense. Here I create a print-name multimethod that switches behavior based for a Person record using the ticket function on the age:

user=> (defrecord Person [name age])
user=> (defmulti print-name (fn [person] (ticket (:age person))))
user=> (defmethod print-name :child [person] (str "Young " (:name person)))
#<MultiFn clojure.lang.MultiFn@44547842>
user=> (defmethod print-name :adult [person] (:name person))
#<MultiFn clojure.lang.MultiFn@44547842>
user=> (defmethod print-name :senior [person] (str "Old " (:name person)))
#<MultiFn clojure.lang.MultiFn@44547842>
user=> (print-name (Person. "Jimmy" 5))
"Young Jimmy"
user=> (print-name (Person. "Alex" 36))
user=> (print-name (Person. "Edna" 99))
"Old Edna"