clojure的defrecord方法名称解析如何工作?

时间:2011-01-29 19:41:54

标签: clojure protocols

在定义记录及其实现的接口之后,我可以通过其名称或使用点运算符的java互操作方式调用其方法。

 user=> (defprotocol Eat (eat [this]))
 Eat
 user=> (defrecord animal [name] Eat (eat [this] "eating"))
 user.animal
 user=> (eat (animal. "bob"))
 "eating"
 user=> (.eat (animal. "bob"))
 "eating"
 user=> 

在表面下,那里发生了什么?是否定义了新的clojure函数?当您定义的函数共享同一个名称时会发生什么(这可能吗?),这些歧义如何解决?

此外,是否可以为其他java对象“导入”java方法,以便您不需要。运营商,以便行为如上? (例如,用于统一用户界面的目的)

1 个答案:

答案 0 :(得分:23)

定义协议时,每个方法都是作为当前命名空间中的函数创建的。因此,您不能在同一名称空间中使用两个协议来定义相同的功能。它还意味着你可以将它们放在不同的命名空间中,并且给定的类型可以扩展它们[1]而不需要任何nameclash因为它们是命名空间(与Java相反,其中单个类不能用同名方法实现两个接口)

从用户的角度来看,协议方法与普通的非多态函数没什么区别。

您可以使用interop调用协议方法这一事实是一个实现细节。原因是对于每个协议,Clojure编译器创建相应的后备接口。稍后,当您定义具有内联协议扩展的新类型时,此类型将实现这些协议的后备接口。

因此,您无法在未以内联方式提供扩展名的对象上使用互操作表单:

(defrecord VacuumCleaner [brand model]
(extend-protocol Eat
  VacuumCleaner
  (eat [this] "eating legos and socks"))

(.eat (VaacumCleaner. "Dyson" "DC-20"))
; method not found exception

编译器对协议函数有特殊支持,因此它们被编译为实例检查,然后是虚拟方法调用,因此适用时(eat ...)将与(.eat ...)一样快。

要回复“可以导入java方法”,可以将它们包装在常规的fns中:

(def callme #(.callme %1 %2 %3))

(显然你可能需要添加其他arities来解释重载并输入提示来删除反射)

[1]但是由于实施限制,您无法扩展内联(其中至少有一个必须采用extend-*形式)