1

How do I create multiple implementations of a multimethod for a single data type?

This may not be a great example, but I hope it illustrates the idea: be able to treat nested vectors both as sequences:

repl> (def thing [[[1] []] [27] [18 [32 35]]])

repl> (fmap count thing)
[2 1 2]

and as trees:

repl> (fmap (partial + 1) thing)
[[[2] []] [28] [19 [33 36]]]

What is the general way to create and use multiple multimethod implementations for the same type?

Matt Fenwick
  • 48,199
  • 22
  • 128
  • 192
  • I don't think multimethods alone will be of much help here - `defmethod` replaces any previous method defined for that dispatch value with the new one. – Alex Dec 14 '12 at 16:05
  • If the functions are doing two totally different things, why not just declare two different functions/multimethods? – DaoWen Dec 14 '12 at 16:29
  • Multimethods are polymorphic on anything at all, not just on type (although this is most common). So, if you have some way of distinguishing between entities of the same type that you want to treat one way and entities of the same type you want to treat another this is fine. – Phil Lord Dec 14 '12 at 16:46
  • @daowen what I was trying to show with this example is that there are at least two meaningful ways to implement [`fmap`](http://clojure.github.com/clojure-contrib/generic.functor-api.html) for vectors. Unfortunately, I wasn't able to come up with an example which is both shorter and clearer. Suggestions are welcome! – Matt Fenwick Dec 14 '12 at 17:09
  • So you want one method `fmap` that have both these behaviours ? One method which does two things depending on what ? – tomferon Dec 14 '12 at 17:37

2 Answers2

1

In your problem you need to dispatch on type of function (fmap first arg) parameter. AFAIK there is no (easy?) way to detect type of argument of any functions (correct me if it's wrong). So you have to add this as some meta data to parameter or something like that. For example

(defmulti fmap (fn [f _] ((comp :arg-seq? meta) f)))

(defmethod fmap true [f col]
  (map-tree f col))

(defmethod fmap false [f col]
  (map f col))

(fmap (with-meta inc {:arg-seq? true}) thing)
-> [[[2] []] [28] [19 [33 36]]]

(fmap (with-meta count {:arg-seq? false}) thing)
-> (2 1 2)

But this looks not so good. If there is other way to get type of function parameter then solution can be more nice looking.

mobyte
  • 3,752
  • 1
  • 18
  • 16
0

The idea of multimethods is that you do "the same operation" on "different arguments" (and not necessarily different types). If you want to do "a different operation" on "the same arguments", then multi-methods won't fit, and like @DaoWen suggested, just using separate functions (or even separate multi-methods) is probably the right way to go about it.

If you have some way of programmatically distinguishing the arguments, and it makes conceptual sense to use different method implementations based on those distinguishing characteristics, then you can use defmulti's dispatch-fn argument to make the distinction.

As a simple example, you can use a key :foo as dispatch-fn, which will do dispatch on the value associated with key :foo in a map that's passed as the single argument to your multi-method.

Joost Diepenmaat
  • 17,633
  • 3
  • 44
  • 53
  • Thanks for the answer! However, I can't just use a different operation -- that would defeat the whole point of the Functor abstraction! I will give `dispatch-fn` a try and see how it works. Thanks for the suggestion! – Matt Fenwick Dec 14 '12 at 17:55