Is it accurate to describe dispatch in Clojure using a Protocol as 'static'?

浪尽此生 提交于 2020-01-23 01:33:51

问题


Meikel Brandmeyer wrote a post on dispatch in Clojure with the URL title Static vs Dynamic. He writes:

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.

He provides the following example of static dispatch in a 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))))

Now it is true that each record maps to a 'class' on the JVM that is compiled. If you try and dispatch on anything other than Left or Right, you'll get a java.lang.IllegalArgumentException with No implementation of method:...found for class:....

I ask because my understanding is that under the covers Clojure is effectively using the same JVM technology for polymorphic dispatch. We could rewrite the above as:

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);
  }
}

Now whilst the types are compiled and statically checked, the actual dispatch is at runtime.

My question is: Is it accurate to describe dispatch in Clojure using a Protocol as 'static'? (assuming you're not using a map for dispatch but are relying on a record or type that corresponds to a class).


回答1:


Clojure's protocol implementation is single dispatch type-driven polymorphism (polymorphic on the type of the first argument to the function) and therefore a form of dynamic polymorphism.

The use of extend-protocol does not result in static binding. The extend-protocol is a macro that just expands into an extend call:

(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))))}))

You are correct that the function to invoke is determined dynamically at runtime using the underlying JVM's dynamic dispatch mechanism. This provides protocols with a performance advantage over multimethods while limiting dispatch to the type of the first argument.

A difference in performance will result from extending a protocol inline in a deftype (or reify) definition versus extending a protocol to an existing type (using extend* variants). An inline deftype is compiled into a Java class along with the protocol methods it implements and therefore directly implements the protocol methods.

Protocol method invocations check if the first argument directly implements the protocol and if it does invokes the method directly on the object rather than looking the appropriate method implementation up.

There is also a detailed benchmark analysis available here. The relevant function in the Clojure source is available here.



来源:https://stackoverflow.com/questions/28130991/is-it-accurate-to-describe-dispatch-in-clojure-using-a-protocol-as-static

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!