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
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)))