I have the option of directly implementing a Protocol in the body of a defrecord instead of using extend-protocol/extend-type
(defprotocol Fly
(fly [this]))
(defrecord Bird [name]
Fly
(fly [this] (format "%s flies" name)))
=>(fly (Bird. "crow"))
"crow flies"
If I now try to override the Fly protocol, I get an error
(extend-type Bird
Fly
(fly [this] (format "%s flies away (:name this)))
class user.Bird already directly implements interface user.Fly for protocol:#'user/Fly
On the other hand if instead I use extend-type initially
(defrecord Dragon [color])
(extend-type Dragon
Fly
(fly [this] (format "%s dragon flies" (:color this))))
=>(fly (Dragon. "Red"))
"Red dragon flies"
I can then "override" the the fly function
(extend-type Dragon
Fly
(fly [this] (format "%s dragon flies away" (:color this))))
=>(fly (Dragon. "Blue"))
"Blue dragon flies away"
My question is, why not allow extension in both cases? Is this a JVM limitation because of the Record <-> Class relation or is there a use case for a non overridable protocol?
On one level, it is an issue of the JVM not allowing method implementations to be swapped in and out of classes, as implementing a protocol inline amounts to having the class created by defrecord
implement an interface corresponding to the protocol. Note that while opting to do so does sacrifice some flexibility, it also buys speed -- and indeed speed is the major design consideration here.
On another level, it is generally a very bad idea for protocol implementations on types to be provided by code which does not own either the type or the protocol in question. See for example this thread on the Clojure Google group for a related discussion (including a statement by Rich Hickey); there's also a relevant entry in the Clojure Library Coding Standards:
Protocols:
- One should only extend a protocol to a type if he owns either the type or the protocol.
- If one breaks the previous rule, he should be prepared to withdraw, should the implementor of either provide a definition
- If a protocol comes with Clojure itself, avoid extending it to types you don't own, especially e.g. java.lang.String and other core Java interfaces. Rest assured if a protocol should extend to it, it will, else lobby for it.
- The motive is, as stated by Rich Hickey, [to prevent] "people extend protocols to types for which they don't make sense, e.g. for which the protocol authors considered but rejected an implementation due to a semantic mismatch.". "No extension will be there (by design), and people without sufficient understanding/skills might fill the void with broken ideas."
This has also been discussed a lot in the Haskell community in connection with type classes (google "orphan instances"; there are some good posts on the topic here on SO too).
Now clearly an inline implementation is always provided by the owner of the type and so should not be replaced by client code. So, I can see two valid use cases remain for protocol method replacement:
testing things out at the REPL and applying quick tweaks;
modifying protocol method implementations in a running Clojure image.
(1) might be less of a problem if one uses extend
& Co. at development time and only switches to inline implementations at some late performance-tuning stage; (2) is just something one may have to sacrifice if top speed is required.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With