使用协议作为“静态”来描述Clojure中的调度是否准确?

时间:2015-01-24 22:29:39

标签: interface clojure static protocols dispatch

Meikel Brandmeyerdispatch in Clojure上发了一篇文章,其网址为“静态与动态”。他写道:

  

协议不是唯一一个我们需要权衡静态与动态的地方。有几个地方可以发现这种权衡。

他在协议中提供了以下静态调度的示例:

(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上。如果您尝试发送LeftRight以外的任何内容,则会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中的调度是否准确?'静态' (假设您没有使用地图进行调度但是依赖于对应于类的记录或类型)。

1 个答案:

答案 0 :(得分:1)

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)定义中将协议扩展为将协议扩展到现有类型(使用extend * variants)会导致性能差异。内联deftype与其实现的协议方法一起编译成Java类,因此直接实现协议方法。

协议方法调用检查第一个参数是否直接实现协议,以及它是否直接在对象上调用方法,而不是查看相应的方法实现。

还有一个详细的基准分析here。 Clojure源中的相关功能可用here