Clojure:cons(seq)与conj(list)的区别

108

我知道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表现不同,或者它们真正可以互换?换句话说,是否存在一个示例,其中列表和序列不能等效使用?

5个回答

164

它们之间的一个区别是,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代表“将项目添加到集合中”。由cons构造的序列以其作为第一个参数传递的元素开头,并有其next / rest部分由将第二个参数应用于seq而得到的结果组成。如上所示,整个序列的类是clojure.lang.Cons。相比之下,conj始终返回与传递给它的集合类型大致相同的集合。(大致相同,因为当PersistentArrayMap增长到超过9个条目时,它将立即转换为PersistentHashMap。)
1传统上,在Lisp世界中,cons构造一对(pair),因此Clojure从Lisp传统中偏离,其cons函数构造的序列没有传统的cdr。将“避免consing”提到时,“cons”一般用于表示“构造某种类型的记录以将多个值组合在一起”,这在编程语言及其实现的研究中目前是普遍存在的。

4
多棒的文章啊!我之前并不知道还有 Cons 类型。做得好! - Daniel Yankowsky
2
顺便提一下,作为一个特殊情况,“(cons foo nil)”返回一个单例“PersistentList”(conj同理)。 - Michał Marczyk
3
另一个非常棒的解释。你真的是一位Clojure绝地! - dbyrne
2
根据我的经验,在性能要求高的情况下,将列表视为列表而不是序列是非常重要的。 - cgrand

12

我的理解是你所说的是正确的:列表中的conj与列表中的cons是等价的。

你可以将conj视为一种“插入某处”的操作,而将cons视为一种“插入头部”的操作。在列表中,将元素插入到头部是最合适的,因此在这种情况下,conj和cons是等价的。


9
另一个区别是,因为conj将序列作为第一个参数,所以在更新ref到某个序列时,它与alter很好地配合使用:
(dosync (alter a-sequence-ref conj an-item))

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


3

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

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

4
cons函数总是返回一个序列,而conj函数返回与提供的序列类型相同的序列。 - Ning Sun

-1

在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]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接