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