Share your thoughts in the 2024 State of Clojure Survey!

Welcome! Please see the About page for a little more info on how this works.

+1 vote
in Protocols by

It seems like a metadata protocol implementation overrides extend calls, but not direct implementations. Is this expected? How does choosing a metadata protocol interact with the caching of defprotocol methods? Are there any other gotchas?

Clojure 1.10 .3
user=> (defprotocol A :extend-via-metadata true (foo [this]))
A
user=> (defrecord Direct [] A (foo [_] :direct))
user.Direct
user=> (foo (->Direct))
:direct
user=> (foo (with-meta (->Direct) {`foo (fn [_] :meta)}))
:direct
user=> (defrecord Extend [])
user.Extend
user=> (extend-protocol A Extend (foo [_] :extend))
nil
user=> (foo (->Extend))
:extend
user=> (foo (with-meta (->Extend) {`foo (fn [_] :meta)}))
:meta

1 Answer

+1 vote
by
selected by
 
Best answer

Yes, this is expected.
https://clojure.org/reference/protocols#_extend_via_metadata

This doesn't really impact the caching of methods, just the selection of the method choice to cache.

by
edited by
There seems to be one strange corner case that isn't (explicitly) covered by the docs: a metadata impl does not win if the protocol is directly implemented but the direct impl is not provided.

Clojure 1.10.3
user=> (defrecord MissingDirect [] A)
user.MissingDirect
user=> (foo (with-meta (->MissingDirect) {`foo (fn [_] :meta)}))
Execution error (AbstractMethodError) at user/eval208 (REPL:1).
Receiver class user.MissingDirect does not define or inherit an implementation of the resolved method 'abstract java.lang.Object foo()' of interface user.A.

OTOH, if a metadata impl is missing, it defaults to the extends impl. The current wording of the docs doesn't seem to make this discrepancy obvious (but clearly this is all to preserve the fast path).
...