Clojure: cons (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)

Есть ли примеры с использованием списков, где conj vs. cons демонстрируют разные типы поведения, или они действительно взаимозаменяемы? С другой стороны, есть ли пример, когда список и seq нельзя использовать эквивалентно?

Ответ 1

Одно отличие состоит в том, что 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, поэтому a count на нем больше не является операцией с постоянным временем (в этом случае оно, вероятно, уменьшится до 1 + 3 - 1 происходит от линейного обхода по первому элемент, 3 получается из (next (cons 4 '(1 2 3)), являющегося PersistentList и, следовательно, Counted).

Предполагает, что cons означает, что cons означает cons (tect a seq) 1 тогда как conj означает соединить (элемент с элементом в коллекцию). Конструкция seq, созданная cons, начинается с элемента, переданного в качестве первого аргумента, и имеет в качестве своей части next/rest вещь, полученную в результате применения seq ко второму аргументу; как показано выше, все дело в классе clojure.lang.Cons. Напротив, conj всегда возвращает коллекцию примерно того же типа, что и переданная ей коллекция. (Грубо, потому что a PersistentArrayMap будет превращен в PersistentHashMap, как только он вырастет выше 9 записей.)


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

Ответ 2

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

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

Ответ 3

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

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

В основном это (conj a-sequence-ref an-item) безопасным потоком. Это не будет работать с cons. Дополнительную информацию см. В главе о Concurrency в Программирование Clojure Стю Хэллоуэй.

Ответ 4

Другим отличием является поведение списка?

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

Ответ 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]