【问题标题】:Is it accurate to describe dispatch in Clojure using a Protocol as 'static'?使用协议将 Clojure 中的调度描述为“静态”是否准确?
【发布时间】:2015-03-23 17:41:02
【问题描述】:

Meikel Brandmeyerdispatch in Clojure 上写了一篇文章,URL 标题为静态与动态。他写道:

协议并不是我们在静态与动态之间进行权衡的唯一地方。有几个地方可以发现这种权衡。

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

(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.IllegalArgumentExceptionNo 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 中使用协议将调度描述为“静态”是否准确?(假设您没有使用地图进行调度,而是依赖于记录或类型对应一个类)。

【问题讨论】:

    标签: interface clojure static protocols dispatch


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

    协议方法调用检查第一个参数是否直接实现协议,如果它确实直接在对象上调用方法,而不是查找适当的方法实现。

    还有一个详细的基准分析可用here。 Clojure 源码中的相关函数在here 可用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-23
      • 2018-07-22
      • 1970-01-01
      • 1970-01-01
      • 2019-05-26
      • 2012-10-28
      相关资源
      最近更新 更多