Dan,

This difference is due to the subtleties around how protocols are implemented, 
and between passing a var vs. capturing a var's state at a particular time.

If you change `transmogrify` to this (note the #'), the `(foo* (BarRec.))` 
succeeds:

        (def foo* (transmogrify #'foo "Bar"))

Protocol implementations are largely tracked by a map held in a var 
corresponding to the protocol's name (in this case, FooProto).  Prior to using 
`extend`, the #'FooProto var contains this map:

{:on-interface user.FooProto, :on user.FooProto, :sigs {:foo {:doc "Make a 
foo", :arglists ([X Y]), :name foo}}, :var #'user/FooProto, :method-map {:foo 
:foo}, :method-builders {#'user/foo #<user$eval1287$fn__1288 
user$eval1287$fn__1288@610f7612>}}

After using `extend`, it contains this map (note the `:impls` slot):

{:impls {user.BarRec {:foo #<user$eval1321$fn__1322 
user$eval1321$fn__1322@1e384de>}}, :on-interface user.FooProto, :on 
user.FooProto, :sigs {:foo {:doc "Make a foo", :arglists ([X Y]), :name foo}}, 
:var #'user/FooProto, :method-map {:foo :foo}, :method-builders {#'user/foo 
#<user$eval1287$fn__1288 user$eval1287$fn__1288@610f7612>}}

The implementation of protocol functions is such that they retain optimized 
(fixed) call paths for each type extended to their protocol.  Thus, when you 
pass the value of `foo` to `transmogrify`, the un-extended protocol's 
"configuration" goes with it.  However, if you pass the var #'foo instead, all 
calls through #'foo are guaranteed to utilize the most up-to-date protocol 
function, and therefore the most up-to-date protocol type extensions.  In any 
case, calling `(foo …)` always works, because that call implicitly goes through 
#'foo anyway.

The fine difference between capturing a var's value vs. passing or calling 
through the var itself is a frequent tripping hazard, but understanding it is 
especially important in ensuring maximum productivity and enjoyment in the REPL 
(as you've found out).  FWIW, we talk about this issue with some simpler 
examples illustrating the subtleties in chapter 10 of the book (in a subsection 
of REPL-Oriented Programming, ~page 416, 'Understand when you’re capturing a 
value instead of dereferencing a var').

Hope this helps,

- Chas

--
http://cemerick.com
[Clojure Programming from O'Reilly](http://www.clojurebook.com)

On Jun 23, 2012, at 7:45 PM, Daniel Skarda wrote:

> Hi,
> 
> I have discovered strange difference between methods implemented directly in 
> defrecord and methods added later using extend (extend-type).
> 
> I simplified the issue to code example listed bellow. Both records FooRec and 
> BarRec implement simple protocol FooProto with method foo. There is also evil 
> function transmogrify, which takes a function F and returns new function 
> which calls F. I use transmogrify to produce function foo*
> 
> If you call foo method directly, everything works as expected. When you call 
> foo*, FooRec method is OK, but BarRec fails with following exception
> 
> No implementation of method: :foo of protocol: #'user/FooProto found for 
> class: user.BarRec
> 
> Which looks weird because you can call foo directly... 
> 
> I expect that defrecord and extend use different approach to extending the 
> type and foo* ends up with a reference to old version of protocol. 
> `defrecord` somehow manages to modify directly the protocol definition 
> referenced by foo*, while `extend` takes more immutable approach and replaces 
> old protocol definition (which foo* cannot see). I briefly dived into Clojure 
> sources and found that indeed `extend` ends with some sort of alter-root-var, 
> while `defrecord` traces ends deep in java sources (deftype*).
> 
> It would be good idea to unify defrecord and extend so they have same 
> behaviour.
> 
> Dan
> 
> (defprotocol FooProto
>   (foo [X Y] "Make a foo"))
> 
> (defn transmogrify [F Y]
>   (fn [A] (F A Y)))
> 
> (def foo* (transmogrify foo "Bar"))
> 
> (defrecord FooRec []
>   FooProto
>   (foo [X Y] (println "Hello," Y)))
> 
> (foo (FooRec.) "World")
> ; -> Hello, World
> 
> (foo* (FooRec.))
> ; -> Hello, Bar
> 
> (defrecord BarRec [])
> 
> (extend-type BarRec
>   FooProto
>   (foo [X Y] (println "Bar" Y)))
> 
> (foo (BarRec.) "Bar")
> ; -> Bar Bar
> 
> (foo* (BarRec.))
> ; bang!
> ; No implementation of method: :foo of protocol: #'user/FooProto found for 
> class: user.BarRec 
>  
> 
> -- 
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clojure@googlegroups.com
> Note that posts from new members are moderated - please be patient with your 
> first post.
> To unsubscribe from this group, send email to
> clojure+unsubscr...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

Reply via email to