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 (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"
Creates and installs a new method of multimethod associated with dispatch-value.
Removes the method of multimethod associated with dispatch-value.
Given a multimethod, returns a map of preferred value -> set of other values
Given a multimethod, returns a map of dispatch values -> dispatch fns
Given a multimethod and a dispatch value, returns the dispatch fn that would apply to that value, ...
A protocol is a named set of named methods and their signatures: (defprotocol AProtocolName ;...
Establishes a parent/child relationship between parent and tag. Parent must be a namespace-qualifi...