Multimethods

Clojure eschews the traditional object-oriented approach of creating a new data type for each new situation, instead preferring to build a large library of functions on a small set of types. However, Clojure fully recognizes the value of runtime polymorphism in enabling flexible and extensible system architecture. Clojure supports sophisticated runtime polymorphism through a multimethod system that supports dispatching on types, values, attributes and metadata of, and relationships between, one or more arguments.


A Clojure multimethod is a combination of a dispatching function, and one or more methods. When a multimethod is defined, using defmulti, a dispatching function must be supplied. This function will be applied to the arguments to the multimethod in order to produce a dispatching value. The multimethod will then try to find the method associated with the dispatching value. If one has been defined (via defmethod), it will then be called with the arguments and that will be the value of the multimethod call. If no method is associated with the dispatching value, the multimethod will look for a method associated with the default dispatching value (which defaults to :default), and will use that if present. Otherwise the call is an error.


This simple system is extremely powerful. Setting implementation inheritance aside, one way to understand the relationship between Clojure multimethods and traditional Java-style single dispatch is that single dispatch is like a Clojure multimethod whose dispatch function calls getClass on the first argument, and whose methods are associated with those classes. Clojure multimethods are not hard-wired to class/type, they can be based on any attribute of the arguments, on multiple arguments, can do validation of arguments and route to error-handling methods etc. 


The multimethod system is exposed by 3 macros:



(defmulti name dispatch-fn default-dispatch-val?)

Creates a new multimethod with the associated dispatch function. If default-dispatch-val is supplied it becomes the default dispatch value of the multimethod, otherwise the default dispatch value is :default.



(defmethod multimethod-name dispatch-value fn-body)

Creates and installs a new method of multimethod associated with dispatch-value. 


(remove-method multimethod-name dispatch-value)

Removes the method of multimethod associated with dispatch-value.




Note: In this example, the keyword :Shape is being used as the dispatch function, as keywords are functions of maps, as described in the Data Structures section.


(defmulti area :Shape)
(defn rect [wd ht] {:Shape :Rect :wd wd :ht ht})
(defn circle [radius] {:Shape :Circle :radius radius})
(defmethod area :Rect [r]
    (* (:wd r) (:ht r)))
(defmethod area :Circle [c]
    (* (. Math PI) (* (:radius c) (:radius c))))
(defmethod area :default [x] :oops)
(def r (rect 4 13))
(def c (circle 12))
(area r)
-> 52
(area c)
-> 452.3893421169302
(area {})
-> :oops

Copyright © Rich Hickey