Multimethods
Prerequisites: Protocols
A multimethod chooses an implementation by calling a dispatch function on its arguments. The dispatch can use any data — a key, a combination of values, or a computed category — not just the type.
(defmulti shipping-cost :method)
(defmethod shipping-cost :standard [order]
(if (>= (:total order) 50) 0 5))
(defmethod shipping-cost :express [order]
15)
(defmethod shipping-cost :pickup [_]
0)
(defmethod shipping-cost :default [order]
(throw (ex-info "Unknown shipping method" {:method (:method order)})))
(doseq [order [{:method :standard :total 30}
{:method :standard :total 80}
{:method :express :total 10}
{:method :pickup :total 99}]]
(println (:method order) (:total order) "->" (shipping-cost order)))
(println (try (shipping-cost {:method :drone})
(catch Exception e (ex-data e))))
:standard 30 -> 5
:standard 80 -> 0
:express 10 -> 15
:pickup 99 -> 0
{:method :drone}
(defmulti name dispatch-fn)— here the dispatch function is the keyword:method.(defmethod name dispatch-value [args] body)adds one case.:defaulthandles unmatched values.- New methods can be added from any namespace, later — the set of cases is open.
Dispatching on several values
(defmulti collide (fn [a b] [(:kind a) (:kind b)]))
(defmethod collide [:asteroid :ship] [_ _] "ship destroyed")
(defmethod collide [:ship :asteroid] [a b] (collide b a))
(defmethod collide [:asteroid :asteroid] [_ _] "asteroids merge")
(defmethod collide :default [_ _] "nothing happens")
(println (collide {:kind :ship} {:kind :asteroid}))
(println (collide {:kind :asteroid} {:kind :asteroid}))
(println (collide {:kind :ship} {:kind :ship}))
ship destroyed
asteroids merge
nothing happens
Hierarchies
(derive ::cat ::mammal)
(derive ::dog ::mammal)
(derive ::mammal ::animal)
(derive ::parrot ::animal)
(defmulti sound :species)
(defmethod sound ::mammal [_] "some mammal noise")
(defmethod sound ::dog [_] "woof")
(defmethod sound ::animal [_] "...")
(doseq [s [::cat ::dog ::parrot]]
(println (name s) "->" (sound {:species s})))
(println (isa? ::cat ::animal))
cat -> some mammal noise
dog -> woof
parrot -> ...
true
derive builds an "is-a" hierarchy of keywords; a multimethod picks the
most specific matching method. ::cat is a keyword qualified with the
current namespace (:user/cat here).
Protocols vs. multimethods
| Protocol | Multimethod | |
|---|---|---|
| Dispatch on | type of the first argument | any function of all arguments |
| Speed | very fast | a map lookup per call |
| Groups functions | yes, several per protocol | one function each |
| Typical use | abstractions over types | business rules over values |