Octales littérales
À un moment donné, je lisais une matrice qui utilisait des zéros non significatifs pour maintenir les lignes et les colonnes appropriées. Mathématiquement, cela est correct, car le zéro non significatif ne modifie évidemment pas la valeur sous-jacente. Les tentatives pour définir une variable avec cette matrice, cependant, échoueraient mystérieusement avec:
java.lang.NumberFormatException: Invalid number: 08
ce qui m'a totalement déconcerté. La raison en est que Clojure traite les valeurs entières littérales avec des zéros non significatifs comme des octals, et il n'y a pas de nombre 08 en octal.
Je dois également mentionner que Clojure prend en charge les valeurs hexadécimales Java traditionnelles via le préfixe 0x . Vous pouvez également utiliser n'importe quelle base entre 2 et 36 en utilisant la notation «base + r + valeur», telle que 2r101010 ou 36r16 qui sont 42 base dix.
Essayer de renvoyer des littéraux dans un littéral de fonction anonyme
Cela marche:
user> (defn foo [key val]
{key val})
#'user/foo
user> (foo :a 1)
{:a 1}
donc je pensais que cela fonctionnerait également:
(#({%1 %2}) :a 1)
mais il échoue avec:
java.lang.IllegalArgumentException: Wrong number of args passed to: PersistentArrayMap
car la macro de lecture # () est étendue à
(fn [%1 %2] ({%1 %2}))
avec le littéral de la carte entre parenthèses. Puisqu'il s'agit du premier élément, il est traité comme une fonction (ce qu'une carte littérale est en fait), mais aucun argument requis (comme une clé) n'est fourni. En résumé, le littéral de fonction anonyme ne se développe pas en
(fn [%1 %2] {%1 %2}) ; notice the lack of parenthesis
et donc vous ne pouvez pas avoir de valeur littérale ([],: a, 4,%) comme corps de la fonction anonyme.
Deux solutions ont été proposées dans les commentaires. Brian Carper suggère d'utiliser des constructeurs d'implémentation de séquence (array-map, hash-set, vector) comme ceci:
(#(array-map %1 %2) :a 1)
tandis que Dan montre que vous pouvez utiliser la fonction d' identité pour dérouler la parenthèse extérieure:
(#(identity {%1 %2}) :a 1)
La suggestion de Brian m'amène en fait à ma prochaine erreur ...
Penser que hash-map ou array-map détermine l' implémentation de la carte concrète immuable
Considérer ce qui suit:
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
Bien que vous n'ayez généralement pas à vous soucier de l'implémentation concrète d'une carte Clojure, vous devez savoir que les fonctions qui développent une carte - comme assoc ou conj - peuvent prendre un PersistentArrayMap et retourner un PersistentHashMap , qui fonctionne plus rapidement pour les cartes plus grandes.
Utiliser une fonction comme point de récursivité plutôt qu'une boucle pour fournir les liaisons initiales
Quand j'ai commencé, j'ai écrit beaucoup de fonctions comme celle-ci:
; 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))))
Alors qu'en fait, la boucle aurait été plus concise et idiomatique pour cette fonction particulière:
; 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))))
Notez que j'ai remplacé l'argument vide, corps de la fonction "constructeur par défaut" (p3 775147 600851475143 3) par une boucle + liaison initiale. La RECUR lie à nouveau maintenant les liaisons de boucle ( au lieu des paramètres fn) et revient au point de récursivité (boucle, au lieu de fn).
Référencement des variables "fantômes"
Je parle du type de var que vous pourriez définir en utilisant le REPL - lors de votre programmation exploratoire - puis référence sans le savoir dans votre source. Tout fonctionne bien jusqu'à ce que vous rechargiez l'espace de noms (peut-être en fermant votre éditeur) et que vous découvriez plus tard un tas de symboles non liés référencés dans votre code. Cela se produit également fréquemment lorsque vous refactorez, en déplaçant une variable d'un espace de noms à un autre.
Traiter la compréhension de la liste for comme une boucle for impérative
Essentiellement, vous créez une liste différée basée sur des listes existantes plutôt que de simplement effectuer une boucle contrôlée. Doseq de Clojure est en fait plus analogue aux constructions de boucle foreach impératives.
Un exemple de leur différence est la possibilité de filtrer les éléments sur lesquels ils itèrent à l'aide de prédicats arbitraires:
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)
Une autre façon dont ils sont différents est qu'ils peuvent fonctionner sur des séquences paresseuses infinies:
user> (take 5 (for [x (iterate inc 0) :when (> (* x x) 3)] (* 2 x)))
(4 6 8 10 12)
Ils peuvent également gérer plus d'une expression de liaison, en itérant d'abord sur l'expression la plus à droite et en travaillant à gauche:
user> (for [x '(1 2 3) y '(\a \b \c)] (str x y))
("1a" "1b" "1c" "2a" "2b" "2c" "3a" "3b" "3c")
Il n'y a pas non plus de pause ou de continuer à sortir prématurément.
Surutilisation des structures
Je viens d'un milieu OOPish donc quand j'ai commencé Clojure mon cerveau pensait toujours en termes d'objets. Je me suis retrouvé à modéliser tout comme une structure parce que son regroupement de «membres», même lâche, me mettait à l'aise. En réalité, les structures doivent être considérées comme une optimisation; Clojure partagera les clés et certaines informations de recherche pour économiser la mémoire. Vous pouvez les optimiser davantage en définissant des accesseurs pour accélérer le processus de recherche de clé.
Dans l'ensemble, vous ne gagnez rien à utiliser une structure sur une carte, sauf pour les performances, donc la complexité supplémentaire pourrait ne pas en valoir la peine.
Utilisation de constructeurs BigDecimal non compatibles
J'avais besoin de beaucoup de BigDecimals et j'écrivais du code moche comme celui-ci:
(let [foo (BigDecimal. "1") bar (BigDecimal. "42.42") baz (BigDecimal. "24.24")]
alors qu'en fait Clojure prend en charge les littéraux BigDecimal en ajoutant M au nombre:
(= (BigDecimal. "42.42") 42.42M) ; true
L'utilisation de la version sucrée élimine une grande partie des ballonnements. Dans les commentaires, twils a mentionné que vous pouvez également utiliser les fonctions bigdec et bigint pour être plus explicite, tout en restant concis.
Utilisation des conversions de noms de package Java pour les espaces de noms
Ce n'est pas en fait une erreur en soi, mais plutôt quelque chose qui va à l'encontre de la structure idiomatique et de la dénomination d'un projet Clojure typique. Mon premier projet Clojure substantiel avait des déclarations d'espace de noms - et des structures de dossiers correspondantes - comme ceci:
(ns com.14clouds.myapp.repository)
qui a gonflé mes références de fonction pleinement qualifiées:
(com.14clouds.myapp.repository/load-by-name "foo")
Pour compliquer encore plus les choses, j'ai utilisé une structure de répertoires Maven standard :
|-- src/
| |-- main/
| | |-- java/
| | |-- clojure/
| | |-- resources/
| |-- test/
...
qui est plus complexe que la structure Clojure "standard" de:
|-- src/
|-- test/
|-- resources/
qui est la valeur par défaut des projets Leiningen et Clojure lui-même.
Les cartes utilisent l'égalité de Java () plutôt que celle de Clojure = pour la correspondance des clés
Initialement rapporté par chouser sur IRC , cette utilisation de equals () de Java conduit à des résultats peu intuitifs:
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
Depuis à la fois Integer et Long instances de 1 sont imprimées de la même manière par défaut, il peut être difficile de détecter pourquoi votre carte ne renvoie aucune valeur. Cela est particulièrement vrai lorsque vous passez votre clé via une fonction qui, peut-être à votre insu, renvoie un long.
Il convient de noter que l'utilisation de equals () de Java au lieu de = de Clojure est essentielle pour que les cartes se conforment à l'interface java.util.Map.
J'utilise Programming Clojure de Stuart Halloway, Practical Clojure de Luke VanderHart et l'aide d'innombrables hackers Clojure sur IRC et la liste de diffusion pour m'aider dans mes réponses.