如何在Clojure对象中查找已实现的协议?

10

有没有记录方式可以找到 Clojure 对象实现了哪些协议?反过来(显示给定协议扩展的类)很容易:(extenders protocol)。

3个回答

6
我最终采用了以下实现方式:
(defn protocol? [maybe-p]
  (boolean (:on-interface maybe-p)))

(defn all-protocols []
  (filter #(protocol? @(val %)) (ns-publics *ns*)))

(defn implemented-protocols [sym]
  (filter #(satisfies? @(val %) sym) (all-protocols)))

首先,它会查找当前命名空间中的所有符号(当然也可以扩展到所有命名空间),无论它们是协议定义还是网络(all-protocols)。接下来,它会查找给定的符号是否满足这些协议。

protocol?函数使用了:on-interface键,据我所知这个键并没有被记录在文档中,因此这个函数是不可移植的。


“protocol?” 在排序映射中失败,至少在Clojure 1.8中是这样。请参见此问题:https://dev59.com/K5ffa4cB1Zd3GeqP6lvL。 - Ben Kovitz

0

目前我无法尝试这个,但你可以尝试Java类方法:getGenericInterfaces。这应该会给你一个接口列表。可能有其他类似的方法来获取这些信息,但我没有查看。

如果你也查看源代码,你会看到协议是如何设置的(你可以通过点击Clojure API中的链接来访问源代码)。在Clojure 1.3中,有一个“私有”函数长这样:

(defn- protocol?
  [maybe-p]
  (boolean (:on-interface maybe-p)))

这个函数被Clojure的extend函数使用,以检查您是否实际提供了协议。如果您自己创建类似的函数,则可以过滤getGenericInterfaces的结果。由于这是一个内部细节,可能会受到更改。


getGenericInterfaces 给了我 (clojure.lang.IObj clojure.lang.ILookup clojure.lang.IKeywordLookup clojure.lang.IPersistentMap java.util.Map java.io.Serializable) 但没有实现的协议。我猜我得深入源代码看看它是怎么做的。 - Maurits Rijk
刚回到我的电脑,我也遇到了和你一样的问题。使用 getInterfaces 可能更简单,作为 getGenericInterfaces 的合理替代方案。我在想如果对这个东西进行 AOT 编译会发生什么。 - hutch
2
协议的部分目的是,一个类不需要实现接口来扩展协议:您可以定义自己的协议,然后说某个预先存在的类通过(extend ExistingClass MyProtocol ...) 扩展它。因此,自然而然地有一种方法来询问“哪些类扩展了这个协议”,但没有一种方法来询问相反的问题。 - Jouni K. Seppänen
@Jouni:谢谢,你的回答对我很有意义。唯一可能的解决方案就是以某种方式获取所有协议的列表,并通过使用(extenders ...)来检查某个类是否实现了它。 - Maurits Rijk

0

这是一个旧问题,而且已经有了一个被接受的答案……但是我可能会这样做:

(defprotocol FooP
  (foo [this]))

(defrecord Foo []
  FooP
  (foo [this] :whatever))

如果您知道要检查的协议,那么可以使用`satisfies?`。
> (satisfies? FooP (->Foo))
true

如果不行的话,你至少可以使用以下方法找到所有接口。
(defn interfaces [o]
  (for [interface (.getInterfaces (class o))]
    (.getCanonicalName ^Class interface)))

你可以检查

> (interfaces (->Foo))
("com.beoliver.FooP" "clojure.lang.IRecord" "clojure.lang.IHashEq" "clojure.lang.IObj" "clojure.lang.ILookup" "clojure.lang.IKeywordLookup" "clojure.lang.IPersistentMap" "java.util.Map" "java.io.Serializable")

> (interfaces [])
("clojure.lang.IObj" "clojure.lang.IEditableCollection" "clojure.lang.IReduce" "clojure.lang.IKVReduce")

> (interfaces {})
("clojure.lang.IObj" "clojure.lang.IEditableCollection" "clojure.lang.IMapIterable" "clojure.lang.IKVReduce")

如果您将bean映射到接口而不是调用.getCanonicalName,则可以看到所有选项。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接