How to figure out what protocols the type implemen

2019-07-25 09:38发布

问题:

Given some type or record, how can I get all the protocols it implements?

Let's say we have the following code:

(defprotocol P1
  (op1 [this]))

(defprotocol P2
  (op2 [this]))

(defrecord R []
  P1
  (op1 [_] 1)
  P2
  (op2 [_] 2))

And what I need is a function that does something like this:

(all-protocols-for-type R) ;; => (P1 P2)

It will be good if there's something backend-agnosting, because I'd like to have a mechanism for both Clojure and ClojureScript.

UPD: the intention for this is to introspect what functionality the particular type provides. Let's say, by doing this:

user> (supers (type {}))
#{clojure.lang.AFn clojure.lang.ILookup java.lang.Object java.util.Map clojure.lang.Seqable java.lang.Runnable clojure.lang.IPersistentCollection java.io.Serializable clojure.lang.IFn clojure.lang.APersistentMap clojure.lang.Associative java.util.concurrent.Callable clojure.lang.IKVReduce clojure.lang.Counted clojure.lang.IMeta clojure.lang.IMapIterable java.lang.Iterable clojure.lang.IPersistentMap clojure.lang.IEditableCollection clojure.lang.IObj clojure.lang.MapEquivalence clojure.lang.IHashEq}

I will know that I can use map as a function (IFn), lookup a value by key there (Associative) and even do reduce-kv on it (IKVReduce).

回答1:

This is the closest you can get in Clojure without doing something crazy:

(require '[clojure.set :as set])

(deftype EmptyType [])

(defrecord EmptyRecord [])

(defn all-protocols-for-type [t]
  (map (comp symbol (memfn getSimpleName))
       (set/difference (supers t)
                       (set/union (supers EmptyType)
                                  (supers EmptyRecord)))))

I used that specific map just to get an output with the same format you gave in your question; you should modify the mapping function as you see fit.

Basically, what you're asking is the other side of the coin to this. Extension of a protocol using deftype or defrecord stores the information on the type, and extension using extend-type or extend-protocol stores the information on the protocol.

So if all your extensions are done directly in deftype or defrecord, you can get all the protocols for a given type using the method I gave above. If all your extensions are done with extend-type or extend-protocol, you can get all the types for a given protocol using the method Alex gave in his answer to that other question. But there isn't a nice way to do both in all cases.



回答2:

This is not possible in general, on the JVM at least, since extending a type to a protocol leaves no information on the type, only on the protocol. In clojurescript I'm not sure. Why do you want this? It's not really very useful to have a list of protocols, if you don't even know what those protocols are for.



回答3:

And what I need is a function that does something like this:

(all-protocols-for-type R) ;; => (P1 P2)

I can't possibly think of any real world utility of a list of protocols implemented for a type, unless this list will be compared to another list of protocol references.

If the protocol references (in the 2nd list) is known the following would do:

(defn extends-all? [protocols typ]
  (reduce (fn [acc p] (and acc (extends? p typ)))
          true
          protocols))
(extends-all? [P1 P2] R) ;; => true

If the goal isn't to compare against a list of protocols, please update the question with the actual use case.