将使用协议的 Clojure 中的调度描述为 'static' 是否准确?
Is it accurate to describe dispatch in Clojure using a Protocol as 'static'?
Meikel Brandmeyer wrote a post on 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.
他在协议中提供了以下静态调度示例:
(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 上的一个'class'。如果您尝试发送 Left
或 Right
以外的任何内容,您将得到一个 java.lang.IllegalArgumentException
和 No implementation of method:...found for class:...
。
我问是因为我的理解是 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 中的调度描述为 'static' 是否准确?(假设您没有使用地图进行调度,而是依赖于对应于 class 的记录或类型)。
Clojure 的协议实现是单一分派类型驱动的多态性(函数第一个参数的类型的多态性),因此是动态多态性的一种形式。
使用extend-protocol
不会导致静态绑定。 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 的动态调度机制动态确定的。这为协议提供了优于多重方法的性能优势,同时将分派限制为第一个参数的类型。
在 deftype
(或 reify
)定义中扩展内联协议与将协议扩展到现有类型(使用扩展*变体)会导致性能差异。内联 deftype
与它实现的协议方法一起被编译成 Java class ,因此直接实现协议方法。
协议方法调用检查第一个参数是否直接实现协议,如果它直接在对象上调用方法而不是查找适当的方法实现。
还有详细的基准分析可用 here. The relevant function in the Clojure source is available here。
Meikel Brandmeyer wrote a post on 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.
他在协议中提供了以下静态调度示例:
(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 上的一个'class'。如果您尝试发送 Left
或 Right
以外的任何内容,您将得到一个 java.lang.IllegalArgumentException
和 No implementation of method:...found for class:...
。
我问是因为我的理解是 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 中的调度描述为 'static' 是否准确?(假设您没有使用地图进行调度,而是依赖于对应于 class 的记录或类型)。
Clojure 的协议实现是单一分派类型驱动的多态性(函数第一个参数的类型的多态性),因此是动态多态性的一种形式。
使用extend-protocol
不会导致静态绑定。 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 的动态调度机制动态确定的。这为协议提供了优于多重方法的性能优势,同时将分派限制为第一个参数的类型。
在 deftype
(或 reify
)定义中扩展内联协议与将协议扩展到现有类型(使用扩展*变体)会导致性能差异。内联 deftype
与它实现的协议方法一起被编译成 Java class ,因此直接实现协议方法。
协议方法调用检查第一个参数是否直接实现协议,如果它直接在对象上调用方法而不是查找适当的方法实现。
还有详细的基准分析可用 here. The relevant function in the Clojure source is available here。