Clojure: contras (seq) vs. conj (lista)

98

Eu sei que consretorna um seq e conjretorna uma coleção. Também sei que conj"adiciona" o item ao final ideal da coleção e conssempre "adiciona" o item à frente. Este exemplo ilustra esses dois pontos:

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)

Para vetores, mapas e conjuntos, essas diferenças fazem sentido para mim. No entanto, para listas, eles parecem idênticos.

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)

Existem exemplos de uso de listas em que conjvs. consexibem comportamentos diferentes ou são verdadeiramente intercambiáveis? Com uma frase diferente, há um exemplo em que uma lista e um seq não podem ser usados ​​de forma equivalente?

dbyrne
fonte

Respostas:

150

Uma diferença é que conjaceita qualquer número de argumentos para inserir em uma coleção, enquanto consleva apenas um:

(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

Outra diferença está na classe do valor de retorno:

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

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

Observe que eles não são realmente intercambiáveis; em particular, clojure.lang.Consnão implementa clojure.lang.Counted, então a counton ele não é mais uma operação de tempo constante (neste caso, provavelmente seria reduzido para 1 + 3 - o 1 vem do percurso linear sobre o primeiro elemento, o 3 vem de (next (cons 4 '(1 2 3))ser um PersistentListe portanto Counted).

A intenção por trás dos nomes é, creio eu, que conssignifica contr (construir a seq) 1 , ao passo que conjsignifica conj (inserir um item em uma coleção). A seqsendo construído por conscomeça com o elemento passado como primeiro argumento e tem como next/ restparte a coisa resultante da aplicação de seqpara o segundo argumento; como mostrado acima, tudo é de classe clojure.lang.Cons. Em contraste, conjsempre retorna uma coleção mais ou menos do mesmo tipo da coleção passada a ele. (Grosso modo, porque a PersistentArrayMapserá transformado em PersistentHashMapassim que crescer além de 9 entradas.)


1 Tradicionalmente, no mundo Lisp, conscons (constrói um par), então Clojure se afasta da tradição Lisp ao ter sua consfunção construir um seq que não possui um tradicional cdr. O uso generalizado de conspara significar "construir um registro de algum tipo ou outro para manter uma série de valores juntos" é atualmente onipresente no estudo de linguagens de programação e sua implementação; é isso que se quer dizer quando se menciona "evitar golpes".

Michał Marczyk
fonte
1
Que artigo fantástico! Eu não sabia que havia um tipo de Cons. Bem feito!
Daniel Yankowsky
Obrigado. Feliz de ouvir isso. :-)
Michał Marczyk
2
A propósito, como um caso especial, (cons foo nil)retorna um singleton PersistentList(e da mesma forma para conj).
Michał Marczyk
1
Outra explicação excelente. Você realmente é um clojure jedi!
dbyrne
1
Em minha experiência, tratar listas como listas e não como sequências é importante quando o desempenho é importante.
cgrand de
11

Meu entendimento é que o que você diz é verdade: conj em uma lista é equivalente a contras em uma lista.

Você pode pensar em conj como sendo uma operação de "inserir em algum lugar" e os contras como uma operação de "inserir na cabeça". Em uma lista, é mais lógico inserir no cabeçalho, então conj e contras são equivalentes neste caso.

Daniel Yankowsky
fonte
8

Outra diferença é que, como conjleva uma sequência como primeiro argumento, funciona bem com a alteratualização de a refpara alguma sequência:

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

Basicamente, isso ocorre (conj a-sequence-ref an-item)de maneira segura para threads. Isso não funcionaria com cons. Veja o capítulo Concurrency in Programming Clojure de Stu Halloway para mais informações.

user323818
fonte
2

Outra diferença é o comportamento da lista?

(list? (conj () 1)) ;=> true
(list? (cons 1 ())) ; => false
FredAKA
fonte
4
cons sempre retorna uma sequência que conj retorna o mesmo tipo daquele fornecido
Ning Dom
-1

Existem funções dedicadas na Biblioteca Tupelo para adicionar valores anexar ou anexar a qualquer coleção sequencial:

(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
fonte