6

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?

3 Answers 3

6

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.

Sign up to request clarification or add additional context in comments.

Comments

5

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.

3 Comments

1. You say that I "avoid the macro". Why is avoiding macros an advantage? 2. Your answer solves the very specific case in my example. With more functions and more dispatch options your would need something like case which would add some overhead to the code
1. Macros have a number of disadvantages over functions. They are not first-class values, they don't exist at run-time, and cannot be passed around - you cannot use a macro with map or reduce, for instance. They are harder to test, and hard to get right. The general rule is only to use macros when you have to - when you want to avoid evaluating arguments, and for making new control structures/syntax.
2. And yes, but instead it's dynamic, which is the point of dynamic dispatch - it's not set at compile time. How you actually implement the dispatch function depends on your underlying logic. If your main goal is performance I would not use multi-methods to being with.
2

Since it is an open question ("is this a good idea?") I'll try to address the 2 concerns that come to mind:

  1. Efficiency: since it results in the same code, it is as efficient typing defmethod for different values and using the same function for them.

  2. 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:

  • your data (dispatch arguments) might be modeled in a better way that reflected that behavior or
  • the multimethod might be doing either too much or to little, resulting on the awkward dispatch call.

I would use such a dispatch mechanism after making sure that is how my data/functions should work.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.