store/[org.clojure/clojure "1.8.0"] clj::clojure.core/defmulti

Community Documentation

Edit

From clojure.org/multimethods:

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 or a value from which the dispatching value is derived. 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 [if not provided]), and will use that if present. Otherwise the call is an error. The multimethod system exposes this API: clj::clojure.core/defmulti creates new multimethods, clj::clojure.core/defmethod creates and installs a new method of multimethod associated with a dispatch-value, clj::clojure.core/remove-method removes the method associated with a dispatch-value. clj::clojure.core/remove-all-methods removes all methods associated all dispatch-values. clj::clojure.core/prefer-method creates an ordering between methods when they would otherwise be ambiguous. Derivation is determined by a combination of either Java inheritance (for class values), or using Clojure's ad hoc hierarchy system. The hierarchy system supports derivation relationships between names (either symbols or keywords), and relationships between classes and names.

Source

(defmacro defmulti
  "Creates a new multimethod with the associated dispatch function.
  The docstring and attr-map are optional.

  Options are key-value pairs and may be one of:

  :default

  The default dispatch value, defaults to :default

  :hierarchy

  The value used for hierarchical dispatch (e.g. ::square is-a ::shape)

  Hierarchies are type-like relationships that do not depend upon type
  inheritance. By default Clojure's multimethods dispatch off of a
  global hierarchy map.  However, a hierarchy relationship can be
  created with the derive function used to augment the root ancestor
  created with make-hierarchy.

  Multimethods expect the value of the hierarchy option to be supplied as
  a reference type e.g. a var (i.e. via the Var-quote dispatch macro #'
  or the var special form)."
  {:arglists '([name docstring? attr-map? dispatch-fn & options])
   :added "1.0"}
  [mm-name & options]
  (let [docstring   (if (string? (first options))
                      (first options)
                      nil)
        options     (if (string? (first options))
                      (next options)
                      options)
        m           (if (map? (first options))
                      (first options)
                      {})
        options     (if (map? (first options))
                      (next options)
                      options)
        dispatch-fn (first options)
        options     (next options)
        m           (if docstring
                      (assoc m :doc docstring)
                      m)
        m           (if (meta mm-name)
                      (conj (meta mm-name) m)
                      m)]
    (when (= (count options) 1)
      (throw (Exception. "The syntax for defmulti has changed. Example: (defmulti name dispatch-fn :default dispatch-value)")))
    (let [options   (apply hash-map options)
          default   (get options :default :default)
          hierarchy (get options :hierarchy #'global-hierarchy)]
      (check-valid-options options :default :hierarchy)
      `(let [v# (def ~mm-name)]
         (when-not (and (.hasRoot v#) (instance? clojure.lang.MultiFn (deref v#)))
           (def ~(with-meta mm-name m)
                (new clojure.lang.MultiFn ~(name mm-name) ~dispatch-fn ~default ~hierarchy)))))))

Example 1

Edit
;; this example illustrates that the dispatch type does not have to be a
;; keyword, but can be anything (in this case, it's a string)

(defmulti greeting
  (fn [x]
    (get x "language")))

;; params is not used, so we could have used [_]
(defmethod greeting "English" [params]
  "Hello!")

(defmethod greeting "French" [params]
  "Bonjour!")

;; default handling
(defmethod greeting :default [params]
  (throw (IllegalArgumentException. 
          (str "I don't know the " (params "language") " language"))))

;; then can use this like this:
(def english-map
  {"id" "1", "language" "English"})

(def  french-map
  {"id" "2", "language" "French"})

(def spanish-map
  {"id" "3", "language" "Spanish"})

(greeting english-map)
;; => "Hello!"

(greeting french-map)
;; => "Bounjour!"

(greeting spanish-map)
;; => java.lang.IllegalArgumentException: I don't know the Spanish language

Example 2

Edit
;; Implementing factorial using multimethods Note that factorial-like function 
;; is best implemented using `recur` for enable tail-call optimization to avoid 
;; stack overflow error. This is a only a demonstration of clojure's multimethod

;; identity form returns the same value passed
(defmulti factorial identity)

(defmethod factorial 0 [_]  1)

(defmethod factorial :default [num] 
  (* num (factorial (dec num))))

(factorial 0)
;; => 1
(factorial 1)
;; => 1
(factorial 3)
;; => 6
(factorial 7)
;; => 5040

Example 3

Edit
;; defmulti has defonce semantics, meaning that it will not be evaluated if
;; the specified multi has a non-nil binding already.

;; this example also demonstrates a defmulti with more than one argument

(defmulti speak identity)
;; => #'user/speak
(defmethod speak :cow [_] "MOO")
;; => #<MultiFn clojure.lang.MultiFn@2ac20952>
(speak :cow)
;; => "MOO"
(defmethod speak :cat [_] "PURR")
;; => #<MultiFn clojure.lang.MultiFn@2ac20952>
(speak :cat)
;; => "PURR"

;; what if we want the cat to be able to meow too?

(defmulti speak (fn [animal mood] animal))
;; => nil
(defmethod speak :cat [_ mood] (get {:happy "PURR" :hungry "MEOW"} mood))
;; => #<MultiFn clojure.lang.MultiFn@2ac20952>
(speak :cat :happy)
;; => ArityException Wrong number of args (2) passed to: core/identity  clojure.lang.AFn.throwArity (AFn.java:429)

;;; the original dispatch has not been replaced!

(def speak nil) ; destroy our defmulti explicitly
;; => #'user/speak
(defmulti speak (fn [animal mood] animal))
;; => #'user/speak
(defmethod speak :cat [_ mood] (get {:happy "PURR" :hungry "MEOW"} mood))
;; => #<MultiFn clojure.lang.MultiFn@66668e94>
(speak :cat :happy)
;; => "PURR"
(speak :cat :hungry)
;; => "MEOW"

;;; how about varargs, so our original cow can still work?

(def speak nil) ; destroy our defmulti explicitly
;;=> #'user/speak
(defmulti speak (fn [animal & args] animal))
;;=> #'user/speak
(defmethod speak :cat [_ mood] (get {:happy "PURR" :hungry "MEOW"} mood))
;;=> #<MultiFn clojure.lang.MultiFn@447536f2>
(speak :cat :hungry)
;;=> "MEOW"
(defmethod speak :cow [_] "MOO")
;; => #<MultiFn clojure.lang.MultiFn@447536f2>
(speak :cow)
;; => "MOO"

Example 4

Edit
;; using multiple functions of the input and a derived key for dispatch,
;; with the :hierarchy key to specify a custom first-class dispatch hierarchy

(def account-types
  (-> (make-hierarchy)
      (derive :Checking :Account)
      (derive :Savings :Account)))

(defmulti service-charge (juxt :account-level :tag) :hierarchy #'account-types)

(defmethod service-charge [:Basic :Checking]  [_] 25)
(defmethod service-charge [:Basic :Savings]   [_] 10)
(defmethod service-charge [:Premium :Account] [_] 0)

(service-charge {:account-level :Basic, :tag :Savings, :balance 1000.01M})
;; => 10
(service-charge {:account-level :Premium, :tag :Savings, :balance 1000.01M})
;; => 0

Uses on crossclj