为什么 Clojure 中的 `disj` 和 `dissoc` 函数不同?
why are `disj` and `dissoc` distinct functions in Clojure?
据我所知,Clojure 的核心函数几乎总是适用于不同类型的集合,例如conj
、first
、rest
等。我有点不解为什么 disj
和 dissoc
是不同的;他们有完全相同的签名:
(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)))
谁在乎呢?就用上面的功能忘记过去吧...
据我所知,Clojure 的核心函数几乎总是适用于不同类型的集合,例如conj
、first
、rest
等。我有点不解为什么 disj
和 dissoc
是不同的;他们有完全相同的签名:
(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)))
谁在乎呢?就用上面的功能忘记过去吧...