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 with testing, for example.
Here's the example:
(ns multi)
(defn my-print [m] (println "The colour is" (:colour m)))
(defmulti which-colour-mm (fn [m f] (:colour m)))
(defmethod which-colour-mm :blue [m f] (f m))
(defmethod which-colour-mm :red [m f] (f m))
(defmethod which-colour-mm :default [m f] (println "Default: Neither Blue nor Red"))
(defn which-colour
([m] (which-colour-mm m my-print))
([m f] (which-colour-mm m f)))
(which-colour {:colour :blue :object :ball})
(which-colour {:colour :yellow :object :ball})
(which-colour {:colour :blue :animal :parrot} (fn [m] (println "The " (:animal m) "is" (:colour m))))
So my defn provides the arity overloading but I'm wondering if defmethod supports anything like this. (I guess you wouldn't want to do it for each defmethod declaration.)
Is this the most suitable (dare I say, idiomatic) approach, or is there a better way?
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 iswhich-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 inwhich-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: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.
Basically you can dispatch on anything, neither the type nor the number of args has to be consistent..like this:
The possibilities are endless
You can do that using multimethods as shown below example: