Octais literais
A certa altura, eu estava lendo uma matriz que usava zeros à esquerda para manter linhas e colunas adequadas. Matematicamente, isso está correto, pois o zero à esquerda obviamente não altera o valor subjacente. As tentativas de definir uma var com esta matriz, no entanto, falhariam misteriosamente com:
java.lang.NumberFormatException: Invalid number: 08
o que me deixou totalmente perplexo. O motivo é que Clojure trata valores inteiros literais com zeros à esquerda como octais, e não há número 08 no octal.
Devo também mencionar que Clojure oferece suporte a valores hexadecimais Java tradicionais por meio do prefixo 0x . Você também pode usar qualquer base entre 2 e 36 usando a notação "base + r + valor", como 2r101010 ou 36r16 que são 42 base dez.
Tentando retornar literais em um literal de função anônima
Isso funciona:
user> (defn foo [key val]
{key val})
#'user/foo
user> (foo :a 1)
{:a 1}
então eu acreditei que isso também funcionaria:
(#({%1 %2}) :a 1)
mas falha com:
java.lang.IllegalArgumentException: Wrong number of args passed to: PersistentArrayMap
porque a macro do leitor # () é expandida para
(fn [%1 %2] ({%1 %2}))
com o literal do mapa entre parênteses. Como é o primeiro elemento, ele é tratado como uma função (o que um mapa literal realmente é), mas nenhum argumento obrigatório (como uma chave) é fornecido. Em resumo, o literal de função anônima não se expande para
(fn [%1 %2] {%1 %2}) ; notice the lack of parenthesis
e, portanto, você não pode ter nenhum valor literal ([],: a, 4,%) como o corpo da função anônima.
Duas soluções foram dadas nos comentários. Brian Carper sugere o uso de construtores de implementação de sequência (mapa de matriz, conjunto de hash, vetor) assim:
(#(array-map %1 %2) :a 1)
enquanto Dan mostra que você pode usar a função de identidade para desembrulhar o parêntese externo:
(#(identity {%1 %2}) :a 1)
A sugestão de Brian realmente me leva ao meu próximo erro ...
Pensar que o hash-map ou array-map determina a implementação do mapa concreto imutável
Considere o seguinte:
user> (class (hash-map))
clojure.lang.PersistentArrayMap
user> (class (hash-map :a 1))
clojure.lang.PersistentHashMap
user> (class (assoc (apply array-map (range 2000)) :a :1))
clojure.lang.PersistentHashMap
Embora você geralmente não precise se preocupar com a implementação concreta de um mapa Clojure, você deve saber que as funções que desenvolvem um mapa - como assoc ou conj - podem pegar um PersistentArrayMap e retornar um PersistentHashMap , que tem um desempenho mais rápido para mapas maiores.
Usando uma função como ponto de recursão em vez de um loop para fornecer ligações iniciais
Quando comecei, escrevi muitas funções como esta:
; Project Euler #3
(defn p3
([] (p3 775147 600851475143 3))
([i n times]
(if (and (divides? i n) (fast-prime? i times)) i
(recur (dec i) n times))))
Quando, na verdade, o loop teria sido mais conciso e idiomático para esta função específica:
; Elapsed time: 387 msecs
(defn p3 [] {:post [(= % 6857)]}
(loop [i 775147 n 600851475143 times 3]
(if (and (divides? i n) (fast-prime? i times)) i
(recur (dec i) n times))))
Observe que substituí o argumento vazio, corpo da função "construtor padrão" (p3 775147 600851475143 3) por um loop + ligação inicial. A recorrência agora religa as ligações de loop (em vez dos parâmetros FN) e salta de volta para o ponto de recursão (circular, em vez de fn).
Referenciando vars "fantasmas"
Estou falando sobre o tipo de var que você pode definir usando o REPL - durante sua programação exploratória - e, sem saber, fazer referência em sua fonte. Tudo funciona bem até que você recarregue o namespace (talvez fechando seu editor) e depois descubra um monte de símbolos não acoplados referenciados em todo o seu código. Isso também acontece com frequência quando você está refatorando, movendo uma var de um namespace para outro.
Tratar a compreensão da lista for como um loop for imperativo
Essencialmente, você está criando uma lista preguiçosa com base em listas existentes, em vez de simplesmente executar um loop controlado. O doseq de Clojure é, na verdade, mais análogo aos construtos foreach imperativos de looping.
Um exemplo de como eles são diferentes é a capacidade de filtrar quais elementos eles iteram usando predicados arbitrários:
user> (for [n '(1 2 3 4) :when (even? n)] n)
(2 4)
user> (for [n '(4 3 2 1) :while (even? n)] n)
(4)
Outra maneira pela qual eles são diferentes é que podem operar em sequências infinitas preguiçosas:
user> (take 5 (for [x (iterate inc 0) :when (> (* x x) 3)] (* 2 x)))
(4 6 8 10 12)
Eles também podem lidar com mais de uma expressão de vinculação, iterando sobre a expressão mais à direita primeiro e trabalhando para a esquerda:
user> (for [x '(1 2 3) y '(\a \b \c)] (str x y))
("1a" "1b" "1c" "2a" "2b" "2c" "3a" "3b" "3c")
Há também não quebrar ou continuar para sair prematuramente.
Uso excessivo de estruturas
Eu venho de uma formação OOPish, então quando comecei o Clojure meu cérebro ainda pensava em termos de objetos. Eu me descobri modelando tudo como uma estrutura porque seu agrupamento de "membros", por mais solto que fosse, me deixava confortável. Na realidade, as estruturas devem ser consideradas principalmente uma otimização; Clojure irá compartilhar as chaves e algumas informações de pesquisa para economizar memória. Você pode otimizá-los ainda mais definindo acessores para acelerar o processo de pesquisa de chave.
No geral, você não ganha nada com o uso de uma estrutura sobre um mapa, exceto em desempenho, então a complexidade adicionada pode não valer a pena.
Usando construtores BigDecimal não açúcares
Eu precisava de muitos BigDecimals e estava escrevendo um código feio como este:
(let [foo (BigDecimal. "1") bar (BigDecimal. "42.42") baz (BigDecimal. "24.24")]
quando, na verdade, Clojure suporta literais BigDecimal anexando M ao número:
(= (BigDecimal. "42.42") 42.42M) ; true
Usar a versão com açúcar elimina muito o inchaço. Nos comentários, twils mencionou que você também pode usar as funções bigdec e bigint para ser mais explícito, mas permanecer conciso.
Usando as conversões de nomenclatura de pacote Java para namespaces
Na verdade, isso não é um erro em si, mas sim algo que vai contra a estrutura idiomática e a nomenclatura de um projeto Clojure típico. Meu primeiro projeto Clojure substancial tinha declarações de namespace - e estruturas de pasta correspondentes - como esta:
(ns com.14clouds.myapp.repository)
o que aumentou minhas referências de função totalmente qualificadas:
(com.14clouds.myapp.repository/load-by-name "foo")
Para complicar ainda mais as coisas, usei uma estrutura de diretório padrão do Maven :
|-- src/
| |-- main/
| | |-- java/
| | |-- clojure/
| | |-- resources/
| |-- test/
...
que é mais complexo do que a estrutura Clojure "padrão" de:
|-- src/
|-- test/
|-- resources/
que é o padrão dos projetos Leiningen e do próprio Clojure .
Os mapas utilizam equals () do Java em vez de Clojure = para correspondência de chave
Relatado originalmente por chouser no IRC , esse uso de equals () do Java leva a alguns resultados não intuitivos:
user> (= (int 1) (long 1))
true
user> ({(int 1) :found} (int 1) :not-found)
:found
user> ({(int 1) :found} (long 1) :not-found)
:not-found
Como as instâncias Integer e Long de 1 são impressas da mesma forma por padrão, pode ser difícil detectar por que seu mapa não está retornando nenhum valor. Isso é especialmente verdadeiro quando você passa sua chave por meio de uma função que, talvez sem seu conhecimento, retorna um long.
Deve-se observar que o uso de equals () do Java em vez de = de Clojure é essencial para que os mapas estejam em conformidade com a interface java.util.Map.
Estou usando Programming Clojure de Stuart Halloway, Practical Clojure de Luke VanderHart e a ajuda de incontáveis hackers de Clojure no IRC e na lista de e-mails para ajudar nas minhas respostas.