ClojureDocs

Nav

Namespaces

defmulti

clojure.core

Available since 1.0 (source)
  • (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).
16 Examples
;; Define the multimethod
(defmulti service-charge (juxt account-level :tag))

;; 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] (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 " (get 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)
"Bonjour!"
=>(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 [existing (get-method xys [x y]
        recover  (get-method xyz [::default y])
        ;; Prevent infinite loops with self-check
        self     (get-method xyz :default)]
    (cond

      (and existing (not= existing self))
      (existing x y)

      (and recover (not= recover self))
      (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))

      :else
      :default-return)))

(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 [geo-obj] (str "Drawing a " (:clr geo-obj) " square"))
(defmethod draw :triangle [geo-obj] (str "Drawing a " (:clr geo-obj) " 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] (get 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 
;; dispatch on the first argument
(defmulti example-multimethod
 (fn [arg-one opts] arg-one))

;; the opts are available in the defmethods
(defmethod example-multimethod :path-one [_ opts]
 (println (:first-opt opts)))

(defmethod example-multimethod :path-two [_ opts]
 (println (:second-opt opts)))

(example-multimethod :path-one {:first-opt 1
                                :second-opt 2})
;; 1
;; => nil
;; You can use multiarity in multimethods  
(defmulti foo (fn [x & _] x))

(defmethod foo :default [_ & _] "DEFAULT VALUE DISPACHED")

;; Like a standar multi-arity function
(defmethod foo :bar 
  ([_ _] "ONE ARGUMENT")
  ([_ _ _] "TWO ARGUMENTs")
  ([_ _ _ _] "THREE ARGUMENTs")
  ([_ _ _ _ & more] (cl-format nil "~d ARGUMENTS" (+ 3 (count more)))))
         
(foo :baz 1)
;; => "DEFAULT VALUE DISPACHED"

(foo :bar 1)
;; => "ONE ARGUMENT"

(foo :bar 1 2)
;; => "TWO ARGUMENTs"

(foo :bar 1 2 3)
;; => "THREE ARGUMENTs"

(foo :bar 1 2 3 4 )
;; => "4 ARGUMENTS"

(foo :bar 1 2 3 4 5)
;; => "5 ARGUMENTS"

(foo :baz 1)
;; => "DEFAULT VALUE DISPACHED"

(foo :bar)
; eval (current-form): (foo :bar)
; (err) Execution error (ArityException) at user/eval22248 
; (err) Wrong number of args (1) passed to: user/eval22204/fn--22205

;; To call the dispatch function without dispatching to a method, 
;; just to examine the dispatch key
(defmulti xyzzy (fn [x y] (+ x y)))
((.dispatchFn xyzzy) 3 7)
;; => 10

;; The above is specific to Clojure on the JVM.  In ClojureScript it is
;; slightly different:
((.-dispatch-fn xyzzy) 3 7)
;; => 10
;; Define the multimethod
(defmulti my-test
          (fn [param1 param2] {:param1 param1 :param2 param2}))

;; Defining handlers/implementation for resulting dispatch values  
(s/defmethod my-test {:param1 :something
                      :param2 :something-else}
  [param1 :- s/Keyword
   param2 :- s/Keyword]
  (+ 3 4))

(s/defmethod my-test {:param1 :this
                      :param2 :that}
  [param1 :- s/Keyword
   param2 :- s/Keyword]
  (+ 4 5))

(s/defmethod my-test :default
  [param1 :- s/Keyword
   param2 :- s/Keyword]
  "ERROR")

;; Invoking it
(my-test :something :something-else)
;; => 7

(my-test :something :undefined)
;; Should go do the default because no other implementation was found, return 
;; => "ERROR"
See Also

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

Added by kumarshantanu

Removes the method of multimethod associated with dispatch-value.

Added by klauern

Removes all of the methods of multimethod.

Added by klauern

Given a multimethod, returns a map of preferred value -> set of other values

Added by klauern

Given a multimethod, returns a map of dispatch values -> dispatch fns

Added by klauern

Given a multimethod and a dispatch value, returns the dispatch fn that would apply to that value, ...

Added by klauern

A protocol is a named set of named methods and their signatures: (defprotocol AProtocolName ;...

Added by klauern

Creates a hierarchy object for use with derive, isa? etc.

Added by kgann

Establishes a parent/child relationship between parent and tag. Parent must be a namespace-qualifi...

Added by timgilbert
1 Note