Precedence of clojure protocols in another namespace

425 Views Asked by At

In a project using clojure.java.jmx, I was extending it's Destract protocols objects->data function to transform more of the JMX data structures returned from calls or metadata queries into plain clojure data structures.

When I was done with the individual data structures, it should have been possible to do a (walk/prewalk jmx/objects->data (jmx/operations "java.lang:type=Threading")).

However, in the Destract protocol there's an implementation of the objects->data function for the type clojure.lang.Associative that would mean maps would be processed incorrectly. I could add an implementation for clojure.lang.IPersistentMap in my namespace, but since clojure.lang.Associative is also an interface for maps this would not work.

I ended up having to fork clojure.java.jmx because of it. If there would be a way to change preference, or retract a protocol for a type within another namespace I wouldn't have had to.

Is there a way to prevent clojure.lang.Associative taking precedence over clojure.lang.IPersistentMap in the protocol ?

If there isn't, is it possible to retract a protocol for a type in another namespace ? Would it even be possible to implement it in regard to the way protocols are compiled into Java interfaces ?

1

There are 1 best solutions below

1
On BEST ANSWER

It should work

Are you sure that providing your own implementation for clojure.lang.IPersistentMap won't work? It works in my REPL session. It even works when I just override the default implementation for clojure.lang.Associative:

user=> (ns ns1)
;;=> nil

ns1=> (defprotocol IPrintable (prnt [this]))
;;=> IPrintable

ns1=> (extend-protocol IPrintable clojure.lang.Associative (prnt [this] (str "clojure.lang.Associative " this)))
;;=> nil

ns1=> (ns ns2)
;;=> nil

ns2=> (ns1/prnt {:a 1})
;;=> "clojure.lang.Associative {:a 1}"

ns2=> (extend-protocol ns1/IPrintable clojure.lang.Associative (prnt [this] (str "My custom impl for clojure.lang.Associative " this)))
;;=> nil

ns2=> (ns1/prnt {:a 1})
;;=> "My custom impl for clojure.lang.Associative {:a 1}"

It also works in a sample project.

You must remember to require the namespace where you extend-protocol if it's not loaded transitively by another namespace. It's important that your extend-protocol will be loaded after the one you would like to override.

Internally extend-protocol or extend-type modify a special map attached to protocol's metadata where it assocs a given type with a function implementation. If there was an existing entry for a given type it will be overridden. Thus the order in which extend-* are executed is important.

When it won't work

When an object implements an interface or a protocol directly (e.g. inline in defrecord) you cannot override the implementation (continued REPL session):

ns2=> (defrecord SomeData []
        ns1/IPrintable (prnt [this] "I'm SomeData"))
;;=> ns2.SomeData

ns2=> (ns1/prnt (SomeData.))
;;=> "I'm SomeData"

ns2=> (extend-protocol ns1/IPrintable SomeData (prnt [this] "Custom impl " this))
;;=> IllegalArgumentException class ns2.SomeData already directly implements interface ns1.IPrintable for protocol:#'ns1/IPrintable  clojure.core/extend (core_deftype.clj:775)