6

假设我写了一个函数:

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

并希望通过说明to必须实现某些协议来记录它(如结构/类型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))))
4

1 回答 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为了惯用而替换为。

当然,正如您在评论中指出的那样,这三个更改有其自身的缺点,所以一定要对它们持保留态度。

于 2017-07-21T22:59:20.360 回答