I have some code that uses multi-methods and would ideally like to overload the function (in this case, multi-function) so that I can pass in a higher order function to help wit
You can do that using multimethods as shown below example:
(defmulti which-colour-mm (fn [m & args] [(count args) (:colour m)]))
(defmethod which-colour-mm [0 :blue] [m] (print m))
(defmethod which-colour-mm [1 :blue] [m f] (f m))
user=> (which-colour-mm {:colour :blue :object :ball})
{:colour :blue, :object :ball}nil
user=> (which-colour-mm {:colour :blue :object :ball} print)
{:colour :blue, :object :ball}nil
This is perfectly fine. There is the "user" interface and the "type" interface of a library. They may be identical, but they don't have to.
The "user" interface is in your case which-colour
. The "type" interface is which-colour-mm
(ok, not really, but just for the sake of the argument). The user of your library does not need to know about the multimethod.
On the other hand someone providing a new colour - say :purple
- does not have to care about multi-arity boilerplate. This is handled for him in which-colour
.
This is a perfectly valid design!
But of course there's a price tag: Suppose you have a colour, which has some more perfomant way to do things... Now, you are locked into a possible slower interface.
To clarify this a little: Suppose you have a collection interface. You provide a function - conj
- which allows the user to add elements to the collection. It is implemented like this:
(defn conj
[coll & elements]
(reduce conj1 coll elements))
conj1
is the "type" interface (eg. a multimethod or protocol function): it adds one element to the collection. So someone supplying a new collection type has only to implement the simple case of adding a single argument. And automagically the new type will also support adding multiple elements.
But now suppose you have a collection type, which allows a faster way to add several elements than just adding one after the other. This capability cannot be used now.
So you make the multimethod/protocol function the function conj
itself. Now the collection can use the faster way. But each implementation must provide the multiple elements boilerplate.
This is a trade-off and up to your decision. There is not Right Way(tm). Both can be considered idiomatic. (Although I personally would try to go with the first one as often as possible.)
YMMV.
Edit: An example of multi arity methods without coding in the dispatch value.
(defmulti which-colour-mm (fn [m & args] (:colour m)))
(defmethod which-colour-mm :blue
([m] (print m))
([m f] (f m)))
Basically you can dispatch on anything, neither the type nor the number of args has to be consistent..like this:
(defn- map-classes [an-object]
(let [cmap
{1 :thing
2 666
3 "yada"}
the-class (class an-object)]
(get cmap an-object the-class)))
(defn- mk-class [& args] (map #(map-classes %) args))
(defmulti play-thing mk-class )
(defmethod play-thing [:thing] [v] (= 1 v))
(defmethod play-thing [666] [v] (= 2 v))
(defmethod play-thing ["yada" String] [v x] (str x v))
The possibilities are endless