3

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 Answer 1

1

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)
Sign up to request clarification or add additional context in comments.

1 Comment

The problem is that the Associative function takes precedence over the IPersistentMap one when a map is encountered, since a map is both. Thanks to this answer I did find out I could overwrite the Associative function with one in my namespace calling identity, making it a noop. A bit hacky, but in the end effectively the same as removing it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.