2010-06-09 5 views
89

Я знаю, что cons возвращает seq и conj возвращает коллекцию. Я также знаю, что conj «добавляет» элемент к оптимальному концу коллекции, а cons всегда «добавляет» элемент вперед. Этот пример иллюстрирует обе эти точки:Clojure: cons (seq) vs. conj (список)

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) 

Существуют ли какие-либо примеры использования списков, где conj против cons демонстрируют различное поведение, или они действительно взаимозаменяемы? По-разному, есть ли пример, когда список и seq нельзя использовать эквивалентно?

ответ

140

Отличие заключается в том, что 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 означает минусы (truct в следующем) , в то время как conj означает CONJ (ойн элемента на коллекцию). Строка seq, созданная cons, начинается с элемента, переданного в качестве первого аргумента, и имеет в качестве своей next/rest часть, полученную в результате применения seq ко второму аргументу; как показано выше, все дело в классе clojure.lang.Cons. Напротив, conj всегда возвращает коллекцию примерно того же типа, что и переданная ей коллекция. (Грубо говоря, потому что PersistentArrayMap будет превращен в PersistentHashMap как только он вырастает за 9 записей.)


Традиционно в Лиспе, cons минусов (tructs пары), так что Clojure уходит из традиции Лиспа в том, что ее функция cons построила seq, который не имеет традиционного cdr. Обобщенное использование cons означает «построить запись того или иного типа для хранения целого ряда значений», в настоящее время является вездесущим при изучении языков программирования и их реализации; это то, что имеется в виду, когда упоминается «избегать consing».

+1

Какая фантастическая запись! Я не знал, что существует тип «Минус». Отлично сработано! –

+0

Спасибо. Рад слышать это. :-) –

+2

Кстати, в качестве специального случая '(cons foo nil)' возвращает одноэлемент 'PersistentList' (а также для' conj'). –

9

Мое понимание состоит в том, что вы говорите правду: conj в списке эквивалентен минусам в списке.

Вы можете думать о том, что conj является операцией «вставить где-то», а cons - как операция «вставить в голову». В списке наиболее логично вставлять в голову, поэтому conj и cons эквивалентны в этом случае.

8

Другим отличием является то, что из-за conj принимает последовательность в качестве первого аргумента, он играет хорошо с alter при обновлении ref в некоторой последовательности:

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

В основном это происходит в поточно-образом (conj a-sequence-ref an-item). Это не будет работать с cons. Дополнительную информацию см. В главе о параллельности в Programming Clojure от Stu Halloway.

1

Другая разница в поведении списка?

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

cons всегда возвращает последовательность, в которой con возвращает тот же тип, который указан –