Clojure: kontra (seq) vs. konj (daftar)

98

Saya tahu bahwa consmengembalikan seq dan conjmengembalikan koleksi. Saya juga tahu bahwa conj"menambahkan" item ke akhir koleksi yang optimal, dan consselalu "menambahkan" item ke depan. Contoh ini menggambarkan kedua poin ini:

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)

Untuk vektor, peta, dan set, perbedaan ini masuk akal bagi saya. Namun, untuk daftar mereka tampak identik.

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)

Apakah ada contoh penggunaan daftar di mana conjvs. consmenunjukkan perilaku yang berbeda, atau apakah mereka benar-benar dapat dipertukarkan? Dengan frasa yang berbeda, apakah ada contoh di mana list dan seq tidak dapat digunakan secara ekuivalen?

dbyrne
sumber

Jawaban:

150

Satu perbedaan adalah yang conjmenerima sejumlah argumen untuk dimasukkan ke dalam koleksi, sementara conshanya membutuhkan satu:

(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

Perbedaan lainnya adalah di kelas nilai yang dikembalikan:

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

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

Perhatikan bahwa ini tidak benar-benar dapat dipertukarkan; secara khusus, clojure.lang.Constidak diimplementasikan clojure.lang.Counted, jadi a counton itu bukan lagi operasi waktu konstan (dalam hal ini mungkin akan berkurang menjadi 1 + 3 - 1 berasal dari traversal linier atas elemen pertama, 3 berasal dari (next (cons 4 '(1 2 3))menjadi a PersistentListdan demikian Counted).

Maksud di balik nama-nama itu, saya percaya, itu consberarti kontra (truct a seq) 1 , sedangkan conjberarti menyambungkan (oin item ke koleksi). The seqsedang dibangun oleh consdimulai dengan elemen lulus sebagai argumen pertama dan memiliki sebagai yang next/ restbagian hal yang dihasilkan dari penerapan sequntuk argumen kedua; seperti yang ditunjukkan di atas, semuanya berkelas clojure.lang.Cons. Sebaliknya, conjselalu mengembalikan koleksi dengan jenis yang kira-kira sama dengan koleksi yang diteruskan kepadanya. (Secara kasar, karena a PersistentArrayMapakan diubah menjadi PersistentHashMapsegera setelah berkembang melampaui 9 entri.)


1 Secara tradisional, dalam dunia Lisp, conskontra (membelah sepasang), maka Clojure berangkat dari tradisi Lisp dalam menjalankan consfungsinya membangun sekuens yang tidak bersifat tradisional cdr. Penggunaan umum consuntuk mengartikan "membuat catatan dari beberapa jenis atau lainnya untuk menyimpan sejumlah nilai bersama" saat ini ada di mana-mana dalam studi bahasa pemrograman dan implementasinya; itulah yang dimaksud ketika "menghindari kontra" disebutkan.

Michał Marczyk
sumber
1
Tulisan yang fantastis! Saya tidak menyadari bahwa ada tipe Kontra. Sudah selesai dilakukan dengan baik!
Daniel Yankowsky
Terima kasih. Senang mendengarnya. :-)
Michał Marczyk
2
Kebetulan, sebagai kasus khusus, (cons foo nil)mengembalikan singleton PersistentList(dan juga untuk conj).
Michał Marczyk
1
Penjelasan hebat lainnya. Anda benar-benar seorang jedi jubah!
dbyrne
1
Dalam pengalaman saya, memperlakukan daftar sebagai daftar dan bukan sebagai urutan adalah penting ketika kinerja itu penting.
cgrand
11

Pemahaman saya adalah apa yang Anda katakan itu benar: konj pada daftar sama dengan kontra pada daftar.

Anda dapat menganggap konj sebagai operasi "sisipkan di suatu tempat", dan kontra sebagai operasi "sisipkan di kepala". Pada daftar, paling logis untuk dimasukkan di head, jadi konj dan kontra setara dalam kasus ini.

Daniel Yankowsky
sumber
8

Perbedaan lainnya adalah karena conjmengambil urutan sebagai argumen pertama, itu berfungsi dengan baik altersaat memperbarui refke beberapa urutan:

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

Ini pada dasarnya dilakukan (conj a-sequence-ref an-item)dengan cara yang aman untuk thread. Ini tidak akan berhasil cons. Lihat bab tentang Concurrency dalam Programming Clojure oleh Stu Halloway untuk info lebih lanjut.

pengguna323818
sumber
2

Perbedaan lainnya adalah perilaku daftar?

(list? (conj () 1)) ;=> true
(list? (cons 1 ())) ; => false
FredAKA
sumber
4
kontra selalu mengembalikan urutan yang konj mengembalikan jenis yang sama dari yang disediakan
Ning Sun
-1

Ada fungsi khusus di Perpustakaan Tupelo untuk menambahkan nilai tambah atau awalan ke koleksi berurutan apa pun:

(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]
Alan Thompson
sumber