On Thursday, October 25, 2012 1:49:53 PM UTC-7, puzzler wrote: > > On Thu, Oct 25, 2012 at 11:38 AM, Brian Craft <[email protected]<javascript:> > > wrote: > >> Multimethods seem like a convenience layer over duck typing: probe the >> object to see what it is, then dispatch. Is that a fair description? >> >> >> > I think that's pretty accurate. Others have pointed out that > multimethods can potentially do more than that, but commonly, it's just a > way to do dispatch that leaves things open for extension. > > Here's the vanilla way to do dispatch with no special constructs: > > (defn circle [radius] > {:tag :circle, :radius radius}) > > (defn square [length] > {:tag :square, :length length}) > > (defn rectangle [length width] > {:tag :rectangle, :length length, :width width}) > > (defn tag-test [tag] > #(= (:tag %) tag)) > > (def circle? (tag-test :circle)) > (def square? (tag-test :square)) > (def rectangle? (tag-test :rectangle)) > > (defn area-circle [{:keys [radius]}] > (* 3.14 radius radius)) > > (defn area-square [{:keys [length]}] > (* length length)) > > (defn area-rectangle [{:keys [length width]}] > (* length width)) > > (defn area [shape] > (cond > (circle? shape) (area-circle shape) > (square? shape) (area-square shape) > (rectangle? shape) (area-rectangle shape))) > > Here's the way with multimethods: > > (defn circle [radius] > {:tag :circle, :radius radius}) > > (defn square [length] > {:tag :square, :length length}) > > (defn rectangle [length width] > {:tag :rectangle, :length length, :width width}) > > (defmulti area :tag) > > (defmethod area :circle [{:keys [radius]}] > (* 3.14 radius radius)) > > (defmethod area :square [{:keys [length]}] > (* length length)) > > (defmethod area :rectangle [{:keys [length width]}] > (* length width)) > > The dispatch function has been eliminated, and we might not need to build > explicit predicates to test for the different types (although in a real > system, you'd probably want those anyway). Other than that, pretty > similar. Multimethods become a clearer win if you need inheritance, or if > you need other people to be able to extend the definition of area to new > types without access to your code. > > Now let's look at the record version: > > (defrecord Circle [radius]) > (defrecord Square [length]) > (defrecord Rectangle [length width]) > > (defn area-circle [{:keys [radius]}] > (* 3.14 radius radius)) > > (defn area-square [{:keys [length]}] > (* length length)) > > (defn area-rectangle [{:keys [length width]}] > (* length width)) > > ;; Dispatch via protocol > > (defprotocol Area > (area [this])) > > (extend Circle > Area > {:area area-circle}) > > (extend Square > Area > {:area area-square}) > > (extend Rectangle > Area > {:area area-rectangle}) > > There are actually several ways to extend protocols to various records. > The most extensible way is to extend records "from the outside" like above, > and to use maps of functions so the functions aren't "locked up" inside the > record but can be reused by other related objects. But as you can see, > this is fairly verbose and it doesn't give you the kind of speed advantage > that people are generally looking for when they convert to > records/protocols. So most people end up declaring everything inline, like > this: > > (defprotocol Area > (area [this])) > > (defrecord Circle [radius] > Area > (area [_] (* 3.14 radius radius))) > > (defrecord Square [length] > Area > (area [_] (* length length))) > > (defrecord Rectangle [length width] > Area > (area [_] (* length width))) > > I personally haven't had a very good experience with records and > protocols, although it is possible that they have improved since I last did > much with them. First, I found that records were mostly similar to maps, > but behaved subtly different in ways that bit me occasionally. Second, the > introduction and repetition of protocol names feel clunky to me, especially > in the common case of one function per protocol. Third, in order to chase > after the supposed performance gains that records/protocols purportedly > bring, I found myself altering my code in ways that looked less and less > Clojurian, using more dot syntax to access constructors, fields, and > methods, and adding type hints. Even with all those changes, I seemed to > keep running into places that Clojure was forced to use dynamic reflection > on my record-based code (which is never a problem with maps), and the > reflection more than cancels out any performance gains from the dispatch. > > So my vote is for multimethods, which is at least as clean and fast as > manual dispatch with more flexibility. Records/protocols may have some > additional performance benefits in the right use cases, but in my > experience it definitely comes at a cost. >
Thanks, this is very helpful! -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to [email protected] Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to [email protected] For more options, visit this group at http://groups.google.com/group/clojure?hl=en
