什么是Clojure关闭记录背后的理由?

gri*_*rit 19 clojure

我可以选择在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社区中已经对类型类(谷歌"孤儿实例")进行了很多讨论;这里也有关于这个主题的一些好帖子.

现在很明显,内联实现总是由类型的所有者提供,因此不应该被客户端代码替换.所以,我可以看到两个有效的用例仍然用于协议方法替换:

  1. 在REPL上测试并应用快速调整;

  2. 修改正在运行的Clojure映像中的协议方法实现.

(1)如果extend在开发时使用&Co.并且仅在某个后期性能调整阶段切换到内联实现,则可能不是问题; (2)如果需要最高速度,则可能需要牺牲一些东西.