为什么 Clojure 中的 `disj` 和 `dissoc` 函数不同?

why are `disj` and `dissoc` distinct functions in Clojure?

据我所知,Clojure 的核心函数几乎总是适用于不同类型的集合,例如conjfirstrest 等。我有点不解为什么 disjdissoc 是不同的;他们有完全相同的签名:

(dissoc map) (dissoc map key) (dissoc map key & ks)
(disj set) (disj set key) (disj set key & ks)

和相当相似的语义。为什么这些都没有包含在同一个函数中?我能看到的唯一支持这一点的论点是地图同时具有 (assoc map key val)(conj map [key val]) 来添加条目,而集合仅支持 (conj set k).

我可以编写一个单行函数来处理这种情况,但 Clojure 在很多时候都非常优雅,以至于当它不是时,我真的很不爽 :)

试图回答他人的动机总是可疑的,尽管我强烈怀疑这是 core.clj 中的引导问题。这两个函数在 core.clj 中定义得相当早,并且几乎完全相同,只是它们都只采用一种类型并直接调用它的方法。

(. clojure.lang.RT (dissoc map key))

(. set (disjoin key))

这两个函数都是core.clj中定义协议之前定义的,因此它们不能使用协议根据类型在它们之间进行调度。在协议存在之前,这两者也在语言规范中定义。它们也经常被调用,以至于有强烈的动机让它们尽可能快。

只是为了对 Arthur 的回答提供平衡:conj 的定义更早(名称 conj 出现在 core.clj 的第 82 行,而 disj 的名称 conj 出现在第 82 行和 1429 for dissoc),但适用于所有 Clojure 集合类型。 :-) 显然它不使用协议——而是使用常规的 Java 接口,就像大多数 Clojure 函数一样(事实上,我相信目前 Clojure 中唯一使用协议的 "core" 功能是 reduce / reduce-kv).

我猜想这是一种审美选择,而且确实可能​​与地图支持 conj 的方式有关——如果他们支持 disj,人们可能会期望它需要可以传递给 conj 的相同参数,这将是有问题的:

;; hypothetical disj on map
(disj {:foo 1
       [:foo 1] 2 
       {:foo 1 [:foo 1] 2} 3}
       }
      {:foo 1 [:foo 1] 2} ;; [:foo 1] similarly problematic
      )

应该return{}{:foo 1 [:foo 1] 2}还是{{:foo 1 [:foo 1] 2} 3}conj 愉快地接受 [:foo 1]{:foo 1 [:foo 1] 2} 作为 conj 到地图上的东西。 (conj 有两个映射参数意味着 merge;实际上 merge 是根据 conj 实现的,添加了对 nil 的特殊处理)。

因此,为地图设置 dissoc 可能是有意义的,这样很明显它删除了一个键而不是 "something that could be conj'd"。

现在,理论上 dissoc 可以在集合上工作,但也许人们可能会期望它们也支持 assoc,这可能真的没有意义。可能值得指出的是向量确实支持 assoc 而不是 dissoc,所以它们并不总是在一起;这里肯定有一些审美张力。

  (defn del
   "Removes elements from coll which can be set, vector, list, map or string"
   [ coll & rest ]
  (let [ [ w & tail ] rest  ]
    (if w 
      (apply del (cond
          (set? coll) (disj coll w)
          (list? coll)  (remove #(= w %) coll)
          (vector? coll) (into [] (remove #(= w % ) coll))
          (map? coll) (dissoc coll w)
          (string? coll) (.replaceAll coll (str w) "")) tail)
            coll)))

谁在乎呢?就用上面的功能忘记过去吧...