哪个协议定义了Clojure中的conj?

6

假设我写了一个函数:

(defn foo [to x] (conj to x))

我想通过说明 to 必须实现某些协议(例如结构/类型中必须支持调用 conj)来记录它。是否有一个网站或数据库有这个信息?显然,我想将这个问题概括为“在哪里可以找到所有Clojure协议的完整参考资料?”

使用Sam Estep的建议作为清晰和具体的例子,它会看起来像:

(defn invert-many-to-one
  "returns a one-to-many mapping where vals are collections of type `(constructor-fn)`,
   (defaults to `hash-set`). Note that `constructor-fn` is a function of 0 args.
  `insert-fn` function can be passed. if only `constructor-fn` is passed
  then `insert-fn` defaults to `conj` and `(constructor-fn)` must be an instance
  of `clojure.lang.IPersistentCollection`"
  ([m] (invert-many-to-one hash-set conj m))
  ([constructor-fn m] {:pre [(instance? clojure.lang.IPersistentCollection (constructor-fn))]}
   (invert-many-to-one constructor-fn conj m))
  ([constructor-fn insert-fn m]
   (persistent!
    (reduce (fn [m [k v]]
              (assoc! m v (insert-fn (clojure.core/get m v (constructor-fn)) k)))
            (transient {}) m))))

2
说实话,我希望我能给你的问题点赞两次;我非常印象深刻。 - Sam Estep
你的问题比较普遍,而你已经得到的答案比我五年前给出的更好,因此我不会将它关闭为重复项,但对于 conj ,这个问题之前已经被问过了:https://dev59.com/K1_Va4cB1Zd3GeqPPg4J。 - amalloy
@amalloy 无论如何...接口不是协议 :P - beoliver
是的,但是“没有协议,它只是一个接口”显然是对同一个问题的回答。 - amalloy
1个回答

6

很不幸,协议直到Clojure 1.2才被引入,而那时,所有内置的数据结构抽象已经被实现为Java接口,而不是协议。这带来了您预期的缺点,但在ClojureScript中重新实现所有这些抽象作为协议是合适的,因为它是在协议引入之后创建的,但对于JVM Clojure来说,将它们改造成协议是不可行的。

如果您查看conj的源代码,您会发现它调用clojure.lang.RT/conj,它要求它的第一个参数实现IPersistentCollection接口。因此,您可以像这样编写您的函数:

(defn foo [to x]
  {:pre [(instance? clojure.lang.IPersistentCollection to)]}
  (conj to x))

为了让你更好地理解,我会指向我以前关于实现Clojure核心接口的问题。如果那里的答案对你的问题不足,请告诉我,我会在这里添加更多细节。

我会对你的invert-many-to-one函数进行一些小调整:

(defn invert-many-to-one
  "Returns a one-to-many mapping where vals are collections of type
  `(constructor-fn)` (defaults to `hash-set`). Note that `constructor-fn` is a
  function of 0 args. `insert-fn` function can be passed. If only
  `constructor-fn` is passed then `insert-fn` defaults to `conj`.
  `(constructor-fn)` must be an instance of
  `clojure.lang.IPersistentCollection`."
  ([m]
   (invert-many-to-one hash-set m))
  ([constructor-fn m]
   (invert-many-to-one constructor-fn conj m))
  ([constructor-fn insert-fn m]
   {:pre [(instance? clojure.lang.IPersistentCollection (constructor-fn))]}
   (persistent!
    (reduce (fn [m [k v]]
              (assoc! m v (insert-fn (get m v (constructor-fn)) k)))
            (transient {}) m))))
  • 我将 arity-1 的函数体改为调用 arity-2 的函数体,而不是 arity-3 的函数体;这样,您只需要在一个地方指定 conj 作为默认值。
  • 我将前置条件移入 arity-3 的函数体中,以便在所有情况下都使用它。
  • 我用语言习惯性的方式,将 clojure.core/get 替换为 get

当然,正如您在评论中指出的那样,这三个更改都有其缺点,所以请谨慎对待。


很遗憾,Clojure Atlas似乎已经不存在了。我找到了Clojure/Java的类/接口层次结构,但是我不记得在哪里找到的了。 - beoliver
2
@beoliver 啊,我没有注意到;这确实是不幸的!然而,它看起来你仍然可以访问其内容。 - Sam Estep
哈哈,我在将内容粘贴到stackoverflow时更改了连接词。决定节省几个CPU周期(并避免实例测试);) get调用是因为阴影效应。如果您将条件移动到arity-3,则会冒险出现insert-fn不需要它成为IPersistentCollection的情况... - beoliver
考虑(invert-many-to-one #(transient #{}) conj! {1 1 3 1}) - beoliver
@beoliver 很好的观点!我没有考虑到那一点。我在我的回答末尾添加了一些免责声明。 - Sam Estep

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