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

Reply via email to