Clojure: wady (seq) vs. koniunkcja (lista)

98

Wiem, że conszwraca sekwencję i conjzwraca kolekcję. Wiem też, że conj„dodaje” pozycję do optymalnego końca kolekcji i conszawsze „dodaje” ją do przodu. Ten przykład ilustruje oba te punkty:

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)

W przypadku wektorów, map i zestawów te różnice mają dla mnie sens. Jednak w przypadku list wydają się identyczne.

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)

Czy istnieją przykłady korzystania z list miarę conjvs. conswykazują różne zachowania, czy są one rzeczywiście stosowane zamiennie? Sformułowany inaczej, czy istnieje przykład, w którym lista i sekwencja nie mogą być używane równoważnie?

dbyrne
źródło

Odpowiedzi:

150

Jedna różnica polega na tym, że conjakceptuje dowolną liczbę argumentów do wstawienia do kolekcji, podczas gdy conszajmuje tylko jeden:

(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

Kolejna różnica dotyczy klasy wartości zwracanej:

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

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

Zauważ, że nie są one w rzeczywistości zamienne; w szczególności clojure.lang.Consnie implementuje clojure.lang.Counted, więc a counton nie jest już operacją o stałym czasie (w tym przypadku prawdopodobnie zmniejszyłaby się do 1 + 3 - 1 pochodzi z liniowego przechodzenia po pierwszym elemencie, 3 pochodzi z (next (cons 4 '(1 2 3))bycia a PersistentListi w ten sposób Counted).

Uważam, że intencją kryjącą się za nazwami jest conscons (truct a seq) 1 , podczas gdy conjoznacza połączenie (łączenie pozycji z kolekcją). seqSkonstruowany przez consrozpoczęciem z elementem przekazanym jako pierwszy argument i ma na next/ restczęści Rzecz wynikające z zastosowania seqdo drugiego argumentu; jak pokazano powyżej, całość jest klasowa clojure.lang.Cons. W przeciwieństwie do tego, conjzawsze zwraca zbiór mniej więcej tego samego typu, co przekazana do niego kolekcja. (Z grubsza, ponieważ a PersistentArrayMapzostanie zamieniony w a, PersistentHashMapgdy tylko przekroczy 9 ​​wpisów.)


1 Tradycyjnie, w świecie Lispa, conscons (tructs a pair), więc Clojure odchodzi od tradycji Lispa polegając na tym, że jego consfunkcja konstruuje sekwencję, która nie ma tradycyjnego cdr. Uogólnione użycie conswyrażenia „skonstruuj rekord jakiegoś lub innego typu, aby przechowywać razem pewną liczbę wartości” jest obecnie wszechobecne w badaniach języków programowania i ich implementacji; to właśnie ma na myśli, gdy wspomina się o „unikaniu szkód”.

Michał Marczyk
źródło
1
Co za fantastyczny napis! Nie wiedziałem, że był typ Cons. Dobra robota!
Daniel Yankowsky
Dzięki. Szczęśliwy słysząc, że. :-)
Michał Marczyk
2
Nawiasem mówiąc, w szczególnym przypadku (cons foo nil)zwraca singleton PersistentList(i podobnie dla conj).
Michał Marczyk
1
Kolejne wspaniałe wyjaśnienie. Naprawdę jesteś clojure jedi!
dbyrne
1
Z mojego doświadczenia wynika, że ​​traktowanie list jako list, a nie jako sekwencji, jest ważne, gdy liczy się wydajność.
cgrand
11

Rozumiem, że to, co mówisz, jest prawdą: spójność na liście jest równoważna minusom na liście.

Możesz myśleć o koniunkcji jako o operacji „wstawiania gdzieś”, a wadach jako o operacji „wstawiania na początku”. Na liście najbardziej logiczne jest wstawienie na początku, więc w tym przypadku spójniki i wady są równoważne.

Daniel Yankowsky
źródło
8

Inną różnicą jest to, że ponieważ conjjako pierwszy argument przyjmuje sekwencję, dobrze gra z alteraktualizacją a refdo jakiejś sekwencji:

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

Zasadniczo działa to (conj a-sequence-ref an-item)w sposób bezpieczny dla wątków. To by nie zadziałało cons. Zobacz rozdział o współbieżności w Programming Clojure autorstwa Stu Halloway, aby uzyskać więcej informacji.

user323818
źródło
2

Inną różnicą jest zachowanie listy?

(list? (conj () 1)) ;=> true
(list? (cons 1 ())) ; => false
FredAKA
źródło
4
cons zawsze zwraca sekwencję, która spójność zwraca ten sam typ co podany
Ning Sun
-1

W bibliotece Tupelo znajdują się dedykowane funkcje, które pozwalają dodawać wartości dołączane lub poprzedzające do dowolnej kolekcji sekwencyjnej:

(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
źródło