Ottali letterali
A un certo punto stavo leggendo in una matrice che utilizzava gli zeri iniziali per mantenere righe e colonne corrette. Matematicamente questo è corretto, poiché lo zero iniziale ovviamente non altera il valore sottostante. I tentativi di definire una var con questa matrice, tuttavia, fallirebbero misteriosamente con:
java.lang.NumberFormatException: Invalid number: 08
il che mi ha totalmente sconcertato. Il motivo è che Clojure tratta i valori interi letterali con zeri iniziali come ottali e non c'è il numero 08 in ottale.
Dovrei anche menzionare che Clojure supporta i tradizionali valori esadecimali Java tramite il prefisso 0x . Puoi anche usare qualsiasi base compresa tra 2 e 36 usando la notazione "base + r + valore", come 2r101010 o 36r16 che sono 42 base dieci.
Tentativo di restituire letterali in una funzione letterale anonima
Funziona:
user> (defn foo [key val]
{key val})
#'user/foo
user> (foo :a 1)
{:a 1}
quindi ho pensato che avrebbe funzionato anche:
(#({%1 %2}) :a 1)
ma fallisce con:
java.lang.IllegalArgumentException: Wrong number of args passed to: PersistentArrayMap
perché la macro del lettore # () viene espansa a
(fn [%1 %2] ({%1 %2}))
con il letterale della mappa racchiuso tra parentesi. Poiché è il primo elemento, viene trattato come una funzione (che in realtà è una mappa letterale), ma non vengono forniti argomenti obbligatori (come una chiave). In sintesi, la funzione letterale anonima non si espande in
(fn [%1 %2] {%1 %2}) ; notice the lack of parenthesis
e quindi non puoi avere alcun valore letterale ([],: a, 4,%) come corpo della funzione anonima.
Due soluzioni sono state fornite nei commenti. Brian Carper suggerisce di utilizzare costruttori di implementazione della sequenza (array-map, hash-set, vector) in questo modo:
(#(array-map %1 %2) :a 1)
mentre Dan mostra che puoi usare la funzione di identità per scartare la parentesi esterna:
(#(identity {%1 %2}) :a 1)
Il suggerimento di Brian in realtà mi porta al mio prossimo errore ...
Pensare che hash-map o array-map determini l' implementazione della mappa concreta immutabile
Considera quanto segue:
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
Sebbene in genere non dovrai preoccuparti dell'implementazione concreta di una mappa Clojure, dovresti sapere che le funzioni che fanno crescere una mappa, come assoc o conj , possono prendere una PersistentArrayMap e restituire una PersistentHashMap , che funziona più velocemente per mappe più grandi.
Usare una funzione come punto di ricorsione piuttosto che un ciclo per fornire i collegamenti iniziali
Quando ho iniziato, ho scritto molte funzioni come questa:
; 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 in effetti il ciclo sarebbe stato più conciso e idiomatico per questa particolare funzione:
; 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))))
Si noti che ho sostituito l'argomento vuoto, corpo della funzione "costruttore predefinito" (p3 775147 600851475143 3) con un ciclo + associazione iniziale. Il ripresentarsi ora rebinds binding ciclo (invece dei parametri fn) e ritorna al punto ricorsione (anello, anziché fn).
Riferimento a vars "fantasma"
Sto parlando del tipo di var che potresti definire usando REPL - durante la tua programmazione esplorativa - quindi riferimento inconsapevolmente nel tuo sorgente. Tutto funziona bene fino a quando non ricarichi lo spazio dei nomi (magari chiudendo l'editor) e in seguito scopri una serie di simboli non associati a cui fa riferimento il codice. Ciò accade spesso anche durante il refactoring, spostando una var da uno spazio dei nomi a un altro.
Trattare la comprensione della lista for come un ciclo for imperativo
In sostanza stai creando un elenco pigro basato su elenchi esistenti piuttosto che eseguire semplicemente un ciclo controllato. La doseq di Clojure è in realtà più analoga ai costrutti di ciclo foreach imperativi.
Un esempio di come sono diversi è la capacità di filtrare gli elementi su cui ripetono utilizzando predicati arbitrari:
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)
Un altro modo in cui sono diversi è che possono operare su infinite sequenze pigre:
user> (take 5 (for [x (iterate inc 0) :when (> (* x x) 3)] (* 2 x)))
(4 6 8 10 12)
Possono anche gestire più di un'espressione vincolante, iterando prima sull'espressione più a destra e procedendo a sinistra:
user> (for [x '(1 2 3) y '(\a \b \c)] (str x y))
("1a" "1b" "1c" "2a" "2b" "2c" "3a" "3b" "3c")
Inoltre, non ci sono interruzioni o si continua a uscire prematuramente.
Uso eccessivo di strutture
Vengo da un background OOP, quindi quando ho iniziato a Clojure il mio cervello stava ancora pensando in termini di oggetti. Mi sono ritrovato a modellare tutto come una struttura perché il suo raggruppamento di "membri", per quanto sciolto, mi faceva sentire a mio agio. In realtà, gli struct dovrebbero essere considerati principalmente un'ottimizzazione; Clojure condividerà le chiavi e alcune informazioni di ricerca per conservare la memoria. È possibile ottimizzarli ulteriormente definendo le funzioni di accesso per accelerare il processo di ricerca delle chiavi.
Nel complesso non si guadagna nulla dall'utilizzo di una struttura su una mappa tranne che per le prestazioni, quindi la complessità aggiuntiva potrebbe non valerne la pena.
Utilizzo di costruttori BigDecimal non zuccherati
Avevo bisogno di molti BigDecimals e stavo scrivendo un codice brutto come questo:
(let [foo (BigDecimal. "1") bar (BigDecimal. "42.42") baz (BigDecimal. "24.24")]
quando infatti Clojure supporta i letterali BigDecimal aggiungendo M al numero:
(= (BigDecimal. "42.42") 42.42M) ; true
Usare la versione zuccherata elimina molto il gonfiore. Nei commenti, twils ha menzionato che puoi anche usare le funzioni bigdec e bigint per essere più espliciti, ma rimanere concisi.
Utilizzo delle conversioni di denominazione del pacchetto Java per gli spazi dei nomi
Questo non è in realtà un errore di per sé, ma piuttosto qualcosa che va contro la struttura idiomatica e la denominazione di un tipico progetto Clojure. Il mio primo sostanziale progetto Clojure aveva dichiarazioni dello spazio dei nomi e strutture di cartelle corrispondenti, come questa:
(ns com.14clouds.myapp.repository)
che ha gonfiato i miei riferimenti di funzione pienamente qualificati:
(com.14clouds.myapp.repository/load-by-name "foo")
Per complicare ancora di più le cose, ho usato una struttura di directory Maven standard :
|-- src/
| |-- main/
| | |-- java/
| | |-- clojure/
| | |-- resources/
| |-- test/
...
che è più complessa della struttura Clojure "standard" di:
|-- src/
|-- test/
|-- resources/
che è l'impostazione predefinita dei progetti di Leiningen e della stessa Clojure .
Le mappe utilizzano Java equals () anziché Clojure = per la corrispondenza delle chiavi
Originariamente riportato da chouser su IRC , questo utilizzo di Java equals () porta ad alcuni risultati non intuitivi:
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
Poiché entrambe le istanze Intero e Lungo di 1 vengono stampate allo stesso modo per impostazione predefinita, può essere difficile rilevare il motivo per cui la mappa non restituisce alcun valore. Ciò è particolarmente vero quando passi la tua chiave attraverso una funzione che, forse a tua insaputa, restituisce un long.
Va notato che l'utilizzo di Java equals () invece di Clojure = è essenziale affinché le mappe siano conformi all'interfaccia java.util.Map.
Sto usando Programming Clojure di Stuart Halloway, Practical Clojure di Luke VanderHart e l'aiuto di innumerevoli hacker Clojure su IRC e la mailing list per aiutare con le mie risposte.