Clojure Exploring Superclasses & Interfaces Lineage
Ever found yourself deep in Clojure code, needing to implement a new type with deftype
or defrecord
, and suddenly your mind goes blank? “Wait, what interfaces does PersistentVector
actually implement? Which protocols should _my_ type satisfy to behave similarly?”
I faced this exact same issue when I wanted to implement a specific type of queue in Clojure.
The Standard Approach clojure.core/supers
If I want to find all superclasses & interfaces of clojure.lang.PersistentVector
or []
then I would user supers
like so:
(supers (type []))
;; => #{clojure.lang.Reversible clojure.lang.APersistentVector
;; java.util.SequencedCollection clojure.lang.IMeta
;; clojure.lang.IPersistentCollection clojure.lang.IObj
;; clojure.lang.ILookup java.lang.Comparable clojure.lang.Indexed
;; java.util.RandomAccess clojure.lang.Sequential
;; clojure.lang.IReduce java.io.Serializable clojure.lang.AFn
;; clojure.lang.IReduceInit java.lang.Iterable clojure.lang.Counted
;; java.util.Collection java.util.List clojure.lang.Associative
;; clojure.lang.IPersistentStack clojure.lang.IFn
;; java.util.concurrent.Callable clojure.lang.Seqable
;; clojure.lang.IPersistentVector clojure.lang.IDrop java.lang.Object
;; clojure.lang.IHashEq clojure.lang.IKVReduce java.lang.Runnable
;; clojure.lang.IEditableCollection}
This gives a set, but it doesn’t reveal the structure.
We can do better.
Visualising the lineage
I needed a way to see the inheritance and implementation hierarchy more clearly. When implementing my own types, I often want to know the _direct_ interfaces and then explore _their_ supers if needed.
So I wrote this small recursive function:
(defn supers-lineage [class]
(reduce (fn [acc kls]
(assoc acc kls (supers-lineage kls)))
{}
(bases class)))
This function takes a class, gets its direct bases, and for each base, recursively calls itself, associating the base class/interface with its own nested hierarchy. bases
internally calls .getSuperclass
and .getInterfaces
and concats them.
Seeing the difference
Let’s try this on our vector type again:
(supers-lineage (type []))
Outputs
{clojure.lang.APersistentVector ; Base Abstract Class
{clojure.lang.AFn ; Implements AFn
{java.lang.Object {}, ; - AFn extends Object
clojure.lang.IFn ; - AFn implements IFn
{java.util.concurrent.Callable {}, ; - IFn extends Callable
java.lang.Runnable {}}}, ; - IFn extends Runnable
clojure.lang.IPersistentVector ; Implements IPersistentVector
{clojure.lang.Associative ; - IPV extends Associative
{clojure.lang.IPersistentCollection {clojure.lang.Seqable {}}, ; - Assoc extends IPColl (which extends Seqable)
clojure.lang.ILookup {}}, ; - Assoc extends ILookup
clojure.lang.Sequential {}, ; - IPV extends Sequential
clojure.lang.IPersistentStack ; - IPV extends IPStack
{clojure.lang.IPersistentCollection {clojure.lang.Seqable {}}}, ; - IPStack extends IPColl (which extends Seqable)
clojure.lang.Reversible {}, ; - IPV extends Reversible
clojure.lang.Indexed ; - IPV extends Indexed
{clojure.lang.Counted {}}}, ; - Indexed extends Counted
java.lang.Iterable {}, ; Implements Iterable
java.util.List ; Implements List
{java.util.SequencedCollection ; - List extends SequencedCollection
{java.util.Collection {java.lang.Iterable {}}}}, ; - SeqColl extends Collection (which extends Iterable)
java.util.RandomAccess {}, ; Implements RandomAccess
java.lang.Comparable {}, ; Implements Comparable
java.io.Serializable {}, ; Implements Serializable
clojure.lang.IHashEq {}}, ; Implements IHashEq
clojure.lang.IObj ; Implements IObj
{clojure.lang.IMeta {}}, ; - IObj extends IMeta
clojure.lang.IEditableCollection {}, ; Implements IEditableCollection
clojure.lang.IReduce ; Implements IReduce
{clojure.lang.IReduceInit {}}, ; - IReduce extends IReduceInit
clojure.lang.IKVReduce {}, ; Implements IKVReduce
clojure.lang.IDrop {}} ; Implements IDrop
Look at that! Now we can clearly trace the lineage. We see that clojure.lang.APersistentVector
directly implements clojure.lang.IPersistentVector
, java.util.List
, clojure.lang.IObj
, etc. Furthermore, we can see that IPersistentVector
itself extends Associative
, Sequential
, Indexed
, and others. And we can even see that Indexed
extends Counted
. This nested structure makes the relationships explicit.
I’m thinking of creating a small cljs tool to visualise this as a tree!