2

I'm writing a clojure function to format various data types as a string.

My naive solution:

(defn p [d]
       (cond
        (vector? d) (str "vector: " d)
        (list? d) (str "list: " d)))

#'user/p
user> (p [1 2 3])
"vector: [1 2 3]"
user> (p '(1 2 3))
"list: (1 2 3)"

I haven't used multimethods before. I this a good use, or is there a another way to avoid the smelly use of cond?

devth
  • 2,738
  • 4
  • 31
  • 51

3 Answers3

5

I'd go for defining a format protocol and extending it to types you need, as suggested by @rodnaph:

(defprotocol Format
  (fmt [this]))

(extend-protocol Format
  clojure.lang.IPersistentVector
    (fmt [this] (str "vector:" this))
  clojure.lang.IPersistentList
    (fmt [this] (str "list:" this)))

However I don't know which will have better performance, multimethod or protocol extension.

The multimethod definition could look like this:

(defmulti fmt class)

(defmethod fmt 
  clojure.lang.IPersistentVector [this]
    (str "vector:" this))
(defmethod fmt 
  clojure.lang.IPersistentList [this]
    (str "list:" this))

EDIT: you might want to check this question about protocols vs multimethods, as there quite nicely are explained common use cases for both. According to that information, it is better to use a protocol in your scenario.

Community
  • 1
  • 1
MisterMetaphor
  • 5,900
  • 3
  • 24
  • 31
  • Thanks for showing how to do it both ways. I like the simplicity of multimethods here. I might only need to change formatting based on whether it's a seq or a string or anything else at this point, so I might be able to avoid hardcoding the interfaces. – devth Jan 31 '12 at 00:08
1

(I'm a noob but) It looks like a protocol would be best suited to this:

http://clojure.org/protocols

Then you can define the different formatting implementations for each data type you'd like to support.

rodnaph
  • 1,237
  • 10
  • 12
1

I'm sure your question is showing a simplified case relative to what you really need to accomplish. For the general solution, I concur that protocols are a decent approach.

You asked about multimethods, but the trouble you'll run into is with the dispatch function. defmulti takes a dispatch function which will be called on the arguments to the real function. The dispatch function must return a value that can then be used to select which method implementation will be invoked.

The trouble is, what do you dispatch on? To discriminate between collection types, you'd end up with something like this:

(defmulti stringify class)
(defmethod stringify clojure.lang.PersistentVector [v] ...)
(defmethod stringify clojure.lang.PersistentArrayMap [m] ...)
;;; More dispatching on concrete class names

Well, as soon as you see specific clojure.lang class names appearing in your code, all kinds of alarm bells should be going off. These are way too specific... they'll break if the Clojure core library changes, they won't work very cleanly with Java interop, they don't cover user-defined types that happen to implement Seqable... in short, they are a breakdown of abstraction.

Any time you would be tempted to dispatch on class names, whether from Clojure, Java, or 3rd party libraries, you should always reach for extend-type instead.

  • 1
    For my own learning - is there a reason you reject the ad-hoc hierarchy system built into Clojure and documented on http://clojure.org/multimethods ? (You don't mention it, and appear to assume dispatch on concrete classes, which seems to be reading Java back into Clojure.) – Alex Stoddard Jan 30 '12 at 15:41
  • 1
    The original question was using literals for the data structures. If there's a way to build an ad-hoc hierarchy on top of {} and [], I don't know it. I was pointing out that using multimethods would necessarily pull in the implementation classes (written in Java) underneath the Clojure data structures. That's why I _didn't_ like multimethods for this. –  Jan 31 '12 at 18:18