Reputation: 1267
I have a case where I want several dispatch values in a multimethod to map to the same method. For example, for a dispatch value of 1 I want it to call method-a, and for dispatch values of 2, 3, or 4 I want it 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)))
You can then use it like this:
(defmulti f identity)
(defmethod f 1 [x] (method-a x))
(defmethod-dispatch-seq f [2 3 4] [x] (method-b x))
Which allow you you to call the following:
(f 1) => (method-a 1)
(f 2) => (method-b 2)
(f 3) => (method-b 3)
(f 4) => (method-b 4)
Is this a good idea?
Upvotes: 6
Views: 3687
Reputation: 4713
This is actually exactly what hierarchies in clojure was made for. The defmulti
takes a :hierarchy
parameter that can establish x is a y relationships.
Only slight problem is that hierarchies don't work with numbers. You can't tell it that the number 0
"is a" :a
for instance. However, hoping that you really only chose the numbers for simplicity and asking the question and possibly 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 atom instead of (var hh)
since it works much better with clojurescript (cljs doesn't like vars in production).
The performance should be decent. Everything the dispatch function outputs something the mapping to which actual method it resolves to (walking the hierarchy) is cached.
Upvotes: 5
Reputation: 2759
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)
That way you avoid the macro, and you use the dispatch function for its intended purpose.
Upvotes: 5
Reputation: 1511
Since it is an open question ("is this a good idea?") I'll try to address the 2 concerns that come to mind:
Efficiency: since it results in the same code, it is as efficient typing defmethod
for different values and using the same function for them.
DRY, readability, code quality: it is better than typing n times the same code with a different match values.
So, if that is the way your function behaves, it looks like a good idea but the fact that your function behaves that way might indicate something flawed in your model:
I would use such a dispatch mechanism after making sure that is how my data/functions should work.
Upvotes: 2