(defmulti name docstring? attr-map? dispatch-fn & options)
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).
;; Define the multimethod
(defmulti service-charge (fn [acct] [(account-level acct) (:tag acct)]))
;; Handlers for resulting dispatch values
(defmethod service-charge [::acc/Basic ::acc/Checking] [_] 25)
(defmethod service-charge [::acc/Basic ::acc/Savings] [_] 10)
(defmethod service-charge [::acc/Premium ::acc/Account] [_] 0)
;this example illustrates that the dispatch type
;does not have to be a symbol, but can be anything (in this case, it's a string)
(defmulti greeting
(fn[x] (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
;; Implementing factorial using multimethods Note that factorial-like function
;; is best implemented using `recur` which enables tail-call optimization to avoid
;; a 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
;; defmulti/defmethods support variadic arguments and dispatch functions.
(defmulti bat
(fn ([x y & xs]
(mapv class (into [x y] xs)))))
(defmethod bat [String String] [x y & xs]
(str "str: " x " and " y))
(defmethod bat [String String String] [x y & xs]
(str "str: " x ", " y " and " (first xs)))
(defmethod bat [String String String String] [x y & xs]
(str "str: " x ", " y ", " (first xs) " and " (second xs)))
(defmethod bat [Number Number] [x y & xs]
(str "number: " x " and " y))
;; you call it like this...
(bat "mink" "stoat")
;; => "str: mink and stoat"
(bat "bear" "skunk" "sloth")
;; => "str: bear, skunk and sloth"
(bat "dog" "cat" "cow" "horse")
;; => "str: dog, cat, cow and horse"
(bat 1 2)
;; => "number: 1 and 2"
;; defmulti - custom hierarchy
(def h (-> (make-hierarchy)
(derive :foo :bar)))
(defmulti f identity :hierarchy #'h) ;; hierarchy must be a reference type
(defmethod f :default [_] "default")
(defmethod f :bar [_] "bar")
(f :unknown) ;; "default"
(f :bar) ;; "bar"
(f :foo) ;; "bar"
;; Note that any deref'able type is fine.
;; Using an atom instead of (var h) is preferable in clojurescript
;; (which adds a lot of meta information to vars)
;; If you're REPLing you might want to re-define the defmulti dispatch function
;; (which defmulti won't allow you to do). For this you can use `ns-unmap`:
(defmulti x (fn[_] :inc))
(defmethod x :inc [y] (inc y))
(defmethod x :dec [y] (dec y))
(x 0) ;; => 1
(defmulti x (fn[_] :dec)) ;; Can't redefine :(
(x 0) ;; => 1 ;; STILL :(
(ns-unmap *ns* 'x) ;; => unmap the var from the namespace
(defmulti x (fn[_] :dec))
(x 0) ;; => Exception, we now need to redefine our defmethods.
;; So in your file while developing you'd put the ns-unmap to the top of the file
;; It's nice for multimethods to have arglists metadata so that calling `doc`
;; prints the arglist, instead of just the docstring. For example:
(defmulti f "Great function" (fn [x] :blah))
(doc f)
;; -------------------------
;; user/f
;; Great function
;; However, we can add `:arglists` metadata via a third (optional) argument to `defmulti` (`attr-map?` in the docstring for `defmulti`):
(defmulti g "Better function" {:arglists '([x])} (fn [x] :blah))
(doc g)
;; -------------------------
;; user/f
;; ([x])
;; Better function
(defmulti compact map?)
(defmethod compact true [map]
(into {} (remove (comp nil? second) map)))
(defmethod compact false [col]
(remove nil? col))
; Usage:
(compact [:foo 1 nil :bar])
; => (:foo 1 :bar)
(compact {:foo 1 :bar nil :baz "hello"})
; => {:foo 1, :baz "hello"}
;; This show how to do a wildcard match to a dispatch value:
(defmulti xyz (fn [x y] [x y]))
;; We don't care about the first argument:
(defmethod xyz [::default :b]
[x y]
:d-b)
;; We have to implement this manually:
(defmethod xyz :default
[x y]
(let [recover (get-method xyz [::default y])]
;; Prevent infinite loop:
(if (and recover (not (= (get-method xyz :default) recover)))
(do
(println "Found a default")
;; Add the default to the internal cache:
;; Clojurescript will want (-add-method ...)
(.addMethod ^MultiFn xyz [x y] recover)
(recover ::default y))
:default)))
(xyz nil :b) ;; => :d-b
;; only prints "Found a default" once!
;; Extremely simple example, dispatching on a single field of the input map.
;; Here we have a polymorphic map that looks like one of these two examples:
;; {:name/type :split :name/first "Bob" :name/last "Dobbs"}
;; {:name/type :full :name/full "Bob Dobbs"}
(defmulti full-name :name/type)
(defmethod full-name :full [name-data]
(:name/full name-data))
(defmethod full-name :split [name-data]
(str (:name/first name-data) " " (:name/last name-data)))
(defmethod full-name :default [_] "???")
(full-name {:name/type :full :name/full "Bob Dobbs"})
;; => "Bob Dobbs"
(full-name {:name/type :split :name/first "Bob" :name/last "Dobbs"})
;; => "Bob Dobbs"
(full-name {:name/type :oops :name/full "Bob Dobbs"})
;; => "???"
;;polymorphism classic example
;;defmulti
(defmulti draw :Shape)
;;defmethod
(defmethod draw :Square [color] (str "Drawing a " (:clr color) " square"))
(defmethod draw :Triangle [color] (str "Drawing a " (:clr color) " triangle"))
(defn square [color] {:Shape :Square :clr color})
(defn triangle [color] {:Shape :Triangle :clr color})
(draw (square "red"))
(draw (triangle "green"))
;;defmulti with dispatch function
(defmulti salary (fn[amount] (amount :t)))
;;defmethod provides a function implementation for a particular value
(defmethod salary "com" [amount] (+ (:b amount) (/ (:b amount) 2)))
(defmethod salary "bon" [amount] (+ (:b amount) 99))
(salary {:t "com" :b 1000}) ;;1500
(salary {:t "bon" :b 1000}) ;;1099