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

问题描述 投票:96回答:5

我知道cons返回一个序列,conj返回一个集合。我也知道conj将项目“添加”到集合的最佳末端,而cons始终将项目“添加”到最前面。此示例说明了这两点:

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)

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

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不能等效使用?

collections clojure sequence
5个回答
146
投票

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

(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

另一个不同之处在于返回值的类别:

(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意味着构造(构成一个序列)1,而conj意味着构造(将一个项目包含在集合中)。由seq构造的cons从作为第一个参数传递的元素开始,并将next应用于第二个参数产生的事物作为rest / seq部分;如上所示,整个事物属于clojure.lang.Cons类。相反,conj始终返回与传递给它的集合大致相同类型的集合。 (大致来说,因为PersistentArrayMap一旦增长到超过9个条目,就会立即变成PersistentHashMap。)


1传统上,在Lisp世界中,cons构造(成对),因此Clojure偏离Lisp传统,因为其cons功能构造了一个不具有传统cdr的序列]。在编程语言及其实现的研究中,普遍使用cons来表示“构造某种类型或其他类型的记录以将多个值存储在一起”。这就是提到“避免打扰”的意思。


10
投票

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

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


8
投票

另一个不同之处在于,由于conj将序列作为第一个参数,因此在将alter更新为某个序列时,它与ref很好地配合使用:

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

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


2
投票

另一个不同之处是列表的行为?

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

-1
投票

dedicated functions in the Tupelo Library可向任何顺序集合添加附加值或前置值:

(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]
© www.soinside.com 2019 - 2024. All rights reserved.