Meikel Brandmeyer在dispatch in Clojure上写了一篇文章URL 标题为静态与动态。他写道:
Protocols are not the only place where we have a trade-off of static vs. dynamic. There are several places where such a trade-off can be spotted.
他提供了协议(protocol)中的以下静态调度示例:
(defprotocol Flipable
(flip [thing]))
(defrecord Left [x])
(defrecord Right [x])
(extend-protocol Flipable
Left
(flip [this] (Right. (:x this)))
Right
(flip [this] (Left. (:x this))))
现在,每条记录都映射到 JVM 上已编译的“类”,这是事实。如果您尝试分派(dispatch) Left
或 Right
以外的任何内容,您将收到 java.lang.IllegalArgumentException
且 No implementation方法:...找到类:...
。
我问这个问题是因为我的理解是 Clojure 在幕后有效地使用相同的 JVM 技术进行多态调度。我们可以将上面的内容重写为:
interface Flippable {
Flippable flip();
}
class Left implements Flippable {
Right flip();
}
class Right implements Flippable {
Left flip();
}
class Demo {
public static void main(String args[]) {
Flippable flippable = new Right();
System.out.println(flippable.flip);
}
}
现在,虽然类型已编译并静态检查,但实际调度是在运行时进行的。
我的问题是:将 Clojure 中使用协议(protocol)的调度描述为“静态”是否准确?(假设您没有使用 map 进行调度,而是依赖于以下记录或类型)对应一个类)。
最佳答案
Clojure 的协议(protocol)实现是单分派(dispatch)类型驱动的多态性(对函数第一个参数的类型进行多态性),因此是动态多态性的一种形式。
使用extend-protocol
不会导致静态绑定(bind)。 extend-protocol
是一个宏,仅扩展为 extend
调用:
(clojure.pprint/pprint
(clojure.walk/macroexpand-all '(extend-protocol Flipable
Left
(flip [this] (Right. (:x this)))
Right
(flip [this] (Left. (:x this))))))
;=>
(do
(clojure.core/extend
Right
Flipable
{:flip (fn* ([this] (new Left (:x this))))})
(clojure.core/extend
Left
Flipable
{:flip (fn* ([this] (new Right (:x this))))}))
您是正确的,要调用的函数是在运行时使用底层 JVM 的动态调度机制动态确定的。这为协议(protocol)提供了优于多方法的性能优势,同时将调度限制为第一个参数的类型。
在 deftype
(或 reify
)定义中内联扩展协议(protocol)与将协议(protocol)扩展到现有类型(使用扩展*变体)会导致性能差异。内联 deftype
与其实现的协议(protocol)方法一起编译成 Java 类,因此直接实现协议(protocol)方法。
协议(protocol)方法调用检查第一个参数是否直接实现协议(protocol),以及是否直接在对象上调用方法,而不是查找适当的方法实现。
关于interface - 使用协议(protocol) 'static' 在 Clojure 中描述调度是否准确?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28130991/