我可以选择在defrecord的主体中直接实现协议,而不是使用extend-protocol/extend-type
(defprotocol Fly
(fly [this]))
(defrecord Bird [name]
Fly
(fly [this] (format "%s flies" name)))
=>(fly (Bird. "crow"))
"crow flies"
Run Code Online (Sandbox Code Playgroud)
如果我现在尝试覆盖Fly协议,我会收到错误
(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
Run Code Online (Sandbox Code Playgroud)
另一方面,如果相反,我最初使用extend-type
(defrecord Dragon [color])
(extend-type Dragon
Fly
(fly [this] (format "%s dragon flies" (:color this))))
=>(fly (Dragon. "Red"))
"Red dragon flies"
Run Code Online (Sandbox Code Playgroud)
然后我可以"覆盖"fly函数
(extend-type Dragon
Fly
(fly [this] (format "%s dragon flies away" (:color this))))
=>(fly (Dragon. "Blue"))
"Blue dragon flies away"
Run Code Online (Sandbox Code Playgroud)
我的问题是,为什么不允许两种情况下的延期?这是由于Record < - > Class关系的JVM限制还是有非可覆盖协议的用例?
Mic*_*zyk 26
在一个层面上,JVM的一个问题是不允许将方法实现交换进出类,因为实现协议内联相当于通过defrecord实现与协议相对应的接口来创建类.请注意,虽然选择这样做确实会牺牲一些灵活性,但它也会提高速度 - 实际上速度是这里的主要设计考虑因素.
在另一个层面上,通过代码提供类型的协议实现通常是一个非常糟糕的想法,该代码不具有所讨论的类型或协议.例如,请参阅Clojure Google小组上的此主题进行相关讨论(包括Rich Hickey的发言); Clojure Library Coding Standards中还有相关条目:
协议:
- 如果他拥有类型或协议,则应该只将协议扩展到类型.
- 如果违反了之前的规则,如果任何一方的实施者提供了定义,他应准备退出
- 如果协议自带Clojure,请避免将其扩展到您不拥有的类型,尤其是例如java.lang.String和其他核心Java接口.如果一个协议应该扩展到它,请放心,否则它会游说它.
- 正如Rich Hickey所说,动机是[防止]"人们将协议扩展到他们没有意义的类型,例如协议作者考虑但由于语义不匹配而拒绝实现." "没有任何延伸(按设计),没有足够理解/技能的人可能会用破碎的想法填补空白."
在Haskell社区中已经对类型类(谷歌"孤儿实例")进行了很多讨论;这里也有关于这个主题的一些好帖子.
现在很明显,内联实现总是由类型的所有者提供,因此不应该被客户端代码替换.所以,我可以看到两个有效的用例仍然用于协议方法替换:
在REPL上测试并应用快速调整;
修改正在运行的Clojure映像中的协议方法实现.
(1)如果extend在开发时使用&Co.并且仅在某个后期性能调整阶段切换到内联实现,则可能不是问题; (2)如果需要最高速度,则可能需要牺牲一些东西.