Clojure multimethod with multiple send values

I have a case where I need several send values ​​in a multimethod to map the same method. For example, to send the value 1, I want this to call method-a, and to send the values ​​2, 3 or 4 I want this to call method -b.

After some Googling, I ended up writing the following macro:

(defmacro defmethod-dispatch-seq [mult-fn dispatch-values & body] `(do (map (fn [x#] (defmethod ~mult-fn x# ~@body )) ~dispatch-values))) 

Then you can use it as follows:

 (defmulti f identity) (defmethod f 1 [x] (method-a x)) (defmethod-dispatch-seq f [2 3 4] [x] (method-b x)) 

Lets you call the following:

 (f 1) => (method-a 1) (f 2) => (method-b 2) (f 3) => (method-b 3) (f 4) => (method-b 4) 

Is that a good idea?

+6
source share
3 answers

I would rather do something like this:

 (defn dispatch-function [value] (if (= 1 value) :case-a :case-b)) (defmulti f dispatch-function) (defmethod f :case-a [x] :doing-something) (defmethod f :case-b [x] :doing-something-else) 

This way you avoid the macro and you use the send function as intended.

+4
source

Actually, this is exactly what hierarchies were created in clojure for. defmulti accepts a parameter :hierarchy , which can establish x - y relationships .

Only a small problem is that hierarchies do not work with numbers. You cannot say that the number 0 "equal" :a , for example. However, hoping that you really only choose numbers for simplicity and ask a question and perhaps have keywords in your actual case, you can do something like this:

 (def hh (-> (make-hierarchy) (derive :0 :a) (derive :1 :a) (derive :2 :b) (derive :3 :b) atom)) (isa? @hh :3 :b) ;; => true (defmulti mm "Demo of using hierarchy" (comp keyword str) :hierarchy hh) (defmethod mm :b [orig] {:b orig}) (defmethod mm :a [orig] {:a orig}) (defmethod mm :default [orig] :oopsie) (mm 2) ;; => {:b 2} (mm 1) ;; => {:a 1} (mm 4) ;; => :oopsie ;; Cool thing is we can steer this at RUNTIME! (swap! hh derive :4 :b) (mm 4) ;; => {:b 4} ;; So we can keep the data close to the definition: (mapv #(swap! hh derive % :c) [:5 :6 :7]) (defmethod mm :c [orig] {:c orig}) (mm 6) ;; => {:c 6} 

Notes:

  • I use an atom instead of (var hh) , since it works much better with clojurescript (cljs doesn't look like production vars).

  • Performance should be decent. All sending functions output what the mapping is cached to, to which the actual method is implemented, which it resolves (going through the hierarchy).

+4
source

Since this is an open-ended question ("is this a good idea?"), I will try to solve two problems that come to mind:

  • Efficiency: since it leads to the same code, it is effective at typing defmethod for different values ​​and using the same function for them.

  • DRY, readability, code quality: better than entering the same code n times with different match values.

So, if that’s how your function works, it looks good, but the fact that your function behaves this way may indicate that your model is distorted:

  • your data (send arguments) can be modeled better, reflecting this behavior or
  • A multidimensional method can do too much or too little, which leads to an inconvenient dispatch call.

I would use such a sending mechanism, making sure that my data / functions should work.

+2
source

Source: https://habr.com/ru/post/983588/


All Articles