关于集合:Clojure:cons(seq)与conj(list)

Clojure: cons(seq) vs. conj(list)

我知道cons返回seq,而conj返回集合。 我还知道conj将项目"添加"到集合的最佳末端,而cons始终将项目"添加"到最前面。 此示例说明了以下两点:

1
2
3
4
user=> (conj [1 2 3] 4) //returns a collection
[1 2 3 4]
user=> (cons 4 [1 2 3]) //returns a seq
(4 1 2 3)

对于矢量,地图和集合,这些差异对我来说很有意义。 但是,对于列表,它们似乎相同。

1
2
3
4
user=> (conj (list 3 2 1) 4) //returns a list
(4 3 2 1)
user=> (cons 4 (list 3 2 1)) //returns a seq
(4 3 2 1)

是否存在使用conjcons表现出不同行为的列表的示例,或者它们是否真正可互换? 用不同的措词,是否有一个示例,其中列表和seq不能等效使用?


一个区别是conj接受任意数量的参数以插入到集合中,而cons仅接受一个:

1
2
3
4
5
(conj '(1 2 3) 4 5 6)
; => (6 5 4 1 2 3)

(cons 4 5 6 '(1 2 3))
; => IllegalArgumentException due to wrong arity

另一个区别在于返回值的类别:

1
2
3
4
5
(class (conj '(1 2 3) 4))
; => clojure.lang.PersistentList

(class (cons 4 '(1 2 3))
; => clojure.lang.Cons

注意,这些并不是真正可以互换的。特别是clojure.lang.Cons并没有实现clojure.lang.Counted,因此它上的count不再是恒定时间的操作(在这种情况下,它可能会减少为1 + 3-1来自第一个线性遍历元素,3来自(next (cons 4 '(1 2 3))PersistentList,因此是Counted)。

我相信名称的意图是cons表示构造(构造一个seq)1,而conj表示构造(将一个项目存储到集合中)。由cons构造的seq始于作为第一个参数传递的元素,并将seq应用于第二个参数所产生的事物作为其next / rest部分;如上所示,整个对象属于clojure.lang.Cons类。相反,conj始终返回与传递给它的集合大致相同类型的集合。 (大致来说,因为PersistentArrayMap一旦超过9个条目,就会被转换为PersistentHashMap。)

1传统上,在Lisp世界中,cons构造(成对),因此Clojure偏离Lisp传统,因为其cons函数构造了没有传统cdr的seq。在编程语言及其实现的研究中,普遍使用cons来表示"构造某种类型的记录或将其他值保存在一起"。这就是提到"避免打扰"的意思。


我的理解是您所说的是正确的:列表上的conj等同于列表上的cons。

您可以将conj看作是"在某处插入"操作,而将cons看作是"在头插入"操作。在列表上,最合逻辑的是插入到头部,因此conj和cons在这种情况下是等效的。


另一个区别是,由于conj将序列作为第一个参数,因此在将ref更新为某些序列时,它与alter可以很好地配合使用:

1
(dosync (alter a-sequence-ref conj an-item))

这基本上以线程安全的方式执行(conj a-sequence-ref an-item)。这不适用于cons。有关更多信息,请参见Stu Halloway编写的Clojure编程中的并发性一章。


列表的另一个区别是行为?

1
2
(list? (conj () 1)) ;=> true
(list? (cons 1 ())) ; => false


Tupelo库中有专用的功能,可以向任何顺序集合添加附加值或前置值:

1
2
3
4
5
(append [1 2] 3  )   ;=> [1 2 3  ]
(append [1 2] 3 4)   ;=> [1 2 3 4]

(prepend   3 [2 1])  ;=> [  3 2 1]
(prepend 4 3 [2 1])  ;=> [4 3 2 1]