Mutlimethods
Today I read about multimethods in Clojure, what they are and how they’re used.
Mutlimethods essentially allow you to create multiple functions with the same
name that perform different operations based on the data it receives. So say
you have a function, add
, and you want it to operate differently between
collections, strings, and numbers, multimethods would be one way to do that.
Coming from a background of strongly typed languages, multimethods seem like they’d also be useful when trying to filter out bad data types. Although, this hasn’t really been an issue for me yet in Clojure.
Implementation
First, we want to create our dispatching function. This basically returns some identifier that maps to one of the multimethods we will define later.
It’s good to be aware that this may return any value, including nil
, and it
will work the same as any other value returned! So if we defined a multimethod
for nil
, then move
would dispatch to that method.
(defmulti move (fn [animal] (:kind animal)))
Next, we want to define our multimethods for move
. Each function starts with
defmethod
and the name of the multimethod. The third parameter is they key
that is returned by the dispatching function above, the :kind
of animal. The
rest of the multimethod should read just like any other Clojure function.
(defmethod move :bird
[animal]
(str (:name animal) " flies into the sunset!"))
(defmethod move :fish
[animal]
(str (:name animal) " swims through the ocean blue!"))
(defmethod move :rodent
[animal]
(str (:name animal) " scurries through the grassy field!"))
(defmethod move :default
[animal]
(str (:name animal) " skips down the yellow brick road!"))
Notice how the last multimethod has a key of :default
. This basically tells
Clojure, “If you can’t find a multimethod matching this key, use this one!”
In this next part, we’ll create a few different animals that map to one of the multimethods we just created.
(def donald {:name "Donald Duck"
:kind :bird})
(def nemo {:name "Nemo"
:kind :fish})
(def despereaux {:name "Despereaux"
:kind :rodent})
(def toto {:name "Toto"
:kind :dog})
Now we can see how move
behaves when we pass it each of the different animals.
=> (move donald)
> "Donald Duck flies into the sunset!"
=> (move nemo)
> "Nemo swims through the ocean blue!"
=> (move despereaux)
> "Despereaux scurries through the grassy field!"
=> (move toto)
> "Toto skips down the yellow brick road!"
When we passed toto
to move
, Clojure uses the :default
multimethod since
we don’t have a method defined for :dog
.
Pretty cool stuff, right? Of course, we probably wouldn’t want to use a multimethod in
this example. This could be easily achieved in one function using cond
, or
something similar. However, in cases where the implementation of a function
vastly differs based on the data the function receives (:kind in this case),
multimethods are a great tool to help separate ideas!