web-dev-qa-db-fra.com

Clojure: contre (seq) vs conj (liste)

Je sais que cons renvoie une séquence et conj renvoie une collection. Je sais également que conj "ajoute" l'élément à la fin optimale de la collection, et cons "ajoute" toujours l'élément à l'avant. Cet exemple illustre ces deux points:

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)

Pour les vecteurs, les cartes et les ensembles, ces différences ont un sens pour moi. Cependant, pour les listes, elles semblent identiques.

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)

Existe-t-il des exemples d'utilisation de listes où conj et cons présentent des comportements différents, ou sont-ils vraiment interchangeables? Exprimé différemment, existe-t-il un exemple où une liste et une séquence ne peuvent pas être utilisées de manière équivalente?

96
dbyrne

Une différence est que conj accepte n'importe quel nombre d'arguments à insérer dans une collection, tandis que cons n'en prend qu'un:

(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

Une autre différence réside dans la classe de la valeur de retour:

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

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

Notez que ceux-ci ne sont pas vraiment interchangeables; en particulier, clojure.lang.Cons n'implémente pas clojure.lang.Counted, donc un count dessus n'est plus une opération à temps constant (dans ce cas, il serait probablement réduit à 1 + 3 - le 1 provient du parcours linéaire sur le premier élément, le 3 provient de (next (cons 4 '(1 2 3)) étant un PersistentList et donc Counted).

L'intention derrière les noms est, je crois, que cons signifie contre (truct a seq)1, tandis que conj signifie conj (insérer un élément dans une collection). seq en cours de construction par cons commence par l'élément passé comme premier argument et a pour partie next/rest la chose résultant de l'application de seq au deuxième argument; comme indiqué ci-dessus, le tout est de classe clojure.lang.Cons. En revanche, conj renvoie toujours une collection à peu près du même type que la collection qui lui est passée. (En gros, parce qu'un PersistentArrayMap sera transformé en PersistentHashMap dès qu'il dépassera 9 entrées.)


1 Traditionnellement, dans le monde LISP, cons contre (traite une paire), donc Clojure s'écarte de la tradition LISP en ayant sa fonction cons construit une séquence qui n'a pas de cdr. L'utilisation généralisée de cons pour signifier "construire un enregistrement d'un type ou d'un autre pour contenir un certain nombre de valeurs" est actuellement omniprésente dans l'étude des langages de programmation et leur implémentation; c'est ce que l'on veut dire quand "éviter de contrer" est mentionné.

146
Michał Marczyk

Ma compréhension est que ce que vous dites est vrai: conj sur une liste équivaut à contre sur une liste.

Vous pouvez considérer conj comme une opération "insérer quelque part", et par contre comme une opération "insérer en tête". Sur une liste, il est plus logique de l'insérer en tête, donc conj et contre sont équivalents dans ce cas.

9
Daniel Yankowsky

Une autre différence est que parce que conj prend une séquence comme premier argument, il joue bien avec alter lors de la mise à jour d'un ref vers une séquence:

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

Cela fait essentiellement (conj a-sequence-ref an-item) d'une manière thread-safe. Cela ne fonctionnerait pas avec cons. Voir le chapitre sur la concurrence dans Programmation Clojure par Stu Halloway pour plus d'informations.

8
user323818

Une autre différence est le comportement de la liste?

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