问题
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