Clojure: con (seq) vs conj (list)

我知道 cons返回一个 seq,而 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表现出不同的行为,或者他们真的可以互换?用不同的措辞,是否有一个列表和序列不能等效使用的例子?

28049 次浏览

My understanding is that what you say is true: conj on a list is equivalent to cons on a list.

You can think of conj as being an "insert somewhere" operation, and cons as being an "insert at the head" operation. On a list, it is most logical to insert at the head, so conj and cons are equivalent in this case.

One difference is that conj accepts any number of arguments to insert into a collection, while cons takes just one:

(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

Another difference is in the class of the return value:

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


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

Note that these are not really interchangeable; in particular, clojure.lang.Cons does not implement clojure.lang.Counted, so a count on it is no longer a constant time operation (in this case it would probably reduce to 1 + 3 -- the 1 comes from linear traversal over the first element, the 3 comes from (next (cons 4 '(1 2 3)) being a PersistentList and thus Counted).

The intention behind the names is, I believe, that cons means to cons(truct a seq)conj1, whereas conj means to conj(oin an item onto a collection). The seq being constructed by cons starts with the element passed as its first argument and has as its next / rest part the thing resulting from the application of seq to the second argument; as displayed above, the whole thing is of class clojure.lang.Cons. In contrast, conj always returns a collection of roughly the same type as the collection passed to it. (Roughly, because a PersistentArrayMap will be turned into a conj0 as soon as it grows beyond 9 entries.)


1 Traditionally, in the Lisp world, cons cons(tructs a pair), so Clojure departs from the Lisp tradition in having its cons function construct a seq which doesn't have a traditional cdr. The generalised usage of cons to mean "construct a record of some type or other to hold a number of values together" is currently ubiquitous in the study of programming languages and their implementation; that's what's meant when "avoiding consing" is mentioned.

Another difference is that because conj takes a sequence as the first argument, it plays nicely with alter when updating a ref to some sequence:

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

This basically does (conj a-sequence-ref an-item) in a thread-safe manner. This wouldn't work with cons. See the chapter on Concurrency in Programming Clojure by Stu Halloway for more info.

Another difference is the behavior of list?

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

There are dedicated functions in the Tupelo Library to add append or prepend values to any sequential collection:

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