Octals literales
En un momento, estaba leyendo en una matriz que usaba ceros a la izquierda para mantener filas y columnas adecuadas. Matemáticamente, esto es correcto, ya que el cero a la izquierda obviamente no altera el valor subyacente. Los intentos de definir una var con esta matriz, sin embargo, fallarían misteriosamente con:
java.lang.NumberFormatException: Invalid number: 08
lo que me desconcertó totalmente. La razón es que Clojure trata los valores enteros literales con ceros a la izquierda como octales, y no hay un número 08 en octal.
También debo mencionar que Clojure admite valores hexadecimales tradicionales de Java a través del prefijo 0x . También puede utilizar cualquier base entre 2 y 36 utilizando la notación "base + r + valor", como 2r101010 o 36r16, que son 42 base diez.
Intentando devolver literales en una función literal anónima
Esto funciona:
user> (defn foo [key val]
{key val})
#'user/foo
user> (foo :a 1)
{:a 1}
así que creí que esto también funcionaría:
(#({%1 %2}) :a 1)
pero falla con:
java.lang.IllegalArgumentException: Wrong number of args passed to: PersistentArrayMap
porque la macro del lector # () se expande a
(fn [%1 %2] ({%1 %2}))
con el literal del mapa entre paréntesis. Dado que es el primer elemento, se trata como una función (que en realidad es un mapa literal), pero no se proporcionan argumentos obligatorios (como una clave). En resumen, la función literal anónima no se expande a
(fn [%1 %2] {%1 %2}) ; notice the lack of parenthesis
por lo que no puede tener ningún valor literal ([],: a, 4,%) como cuerpo de la función anónima.
Se han dado dos soluciones en los comentarios. Brian Carper sugiere usar constructores de implementación de secuencia (array-map, hash-set, vector) así:
(#(array-map %1 %2) :a 1)
mientras que Dan muestra que puede usar la función de identidad para desenvolver el paréntesis externo:
(#(identity {%1 %2}) :a 1)
La sugerencia de Brian en realidad me lleva a mi próximo error ...
Pensando que el mapa hash o el mapa de matriz determinan la implementación del mapa concreto invariable
Considera lo siguiente:
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
Si bien generalmente no tendrá que preocuparse por la implementación concreta de un mapa Clojure, debe saber que las funciones que hacen crecer un mapa, como assoc o conj , pueden tomar un PersistentArrayMap y devolver un PersistentHashMap , que funciona más rápido para mapas más grandes.
Usar una función como punto de recursión en lugar de un bucle para proporcionar enlaces iniciales
Cuando comencé, escribí muchas funciones 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))))
Cuando, de hecho, el bucle habría sido más conciso e idiomático para esta función en particular:
; 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 reemplacé el argumento vacío, el cuerpo de la función "constructor predeterminado" (p3 775147 600851475143 3) con un bucle + enlace inicial. El repiten ahora vuelve a vincular los enlaces de bucle (en lugar de los parámetros fn) y salta de nuevo al punto de recursión (circular, en lugar de fn).
Haciendo referencia a vars "fantasmas"
Estoy hablando del tipo de var que podría definir usando el REPL, durante su programación exploratoria, y luego, sin saberlo, hacer referencia en su fuente. Todo funciona bien hasta que recarga el espacio de nombres (tal vez cerrando su editor) y luego descubre un montón de símbolos no vinculados a los que se hace referencia en todo su código. Esto también sucede con frecuencia cuando está refactorizando, moviendo una var de un espacio de nombres a otro.
Tratar la comprensión de la lista for como un imperativo bucle for
Básicamente, está creando una lista diferida basada en listas existentes en lugar de simplemente realizar un ciclo controlado. El doseq de Clojure es en realidad más análogo a las construcciones imperativas de bucle foreach.
Un ejemplo de cómo son diferentes es la capacidad de filtrar sobre qué elementos iteran utilizando predicados arbitrarios:
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)
Otra forma en que son diferentes es que pueden operar en infinitas secuencias perezosas:
user> (take 5 (for [x (iterate inc 0) :when (> (* x x) 3)] (* 2 x)))
(4 6 8 10 12)
También pueden manejar más de una expresión de enlace, iterando primero sobre la expresión más a la derecha y trabajando hacia la izquierda:
user> (for [x '(1 2 3) y '(\a \b \c)] (str x y))
("1a" "1b" "1c" "2a" "2b" "2c" "3a" "3b" "3c")
Tampoco hay interrupción o continuar saliendo prematuramente.
Uso excesivo de estructuras
Vengo de un entorno OOPish, así que cuando comencé Clojure, mi cerebro todavía estaba pensando en términos de objetos. Me encontré modelando todo como una estructura porque su agrupación de "miembros", por más suelta que fuera, me hacía sentir cómodo. En realidad, las estructuras deben considerarse principalmente una optimización; Clojure compartirá las claves y cierta información de búsqueda para conservar la memoria. Puede optimizarlos aún más mediante la definición de accesos para acelerar el proceso de búsqueda de claves.
En general, no se gana nada con el uso de una estructura sobre un mapa, excepto el rendimiento, por lo que la complejidad adicional podría no valer la pena.
Usando constructores BigDecimal sin azúcar
Necesitaba muchos BigDecimals y estaba escribiendo un código feo como este:
(let [foo (BigDecimal. "1") bar (BigDecimal. "42.42") baz (BigDecimal. "24.24")]
cuando, de hecho, Clojure admite literales BigDecimal agregando M al número:
(= (BigDecimal. "42.42") 42.42M) ; true
El uso de la versión azucarada elimina gran parte de la hinchazón. En los comentarios, twils mencionó que también puede usar las funciones bigdec y bigint para ser más explícito, pero permanecer conciso.
Uso de las conversiones de nombres de paquetes de Java para espacios de nombres
En realidad, esto no es un error per se, sino algo que va en contra de la estructura idiomática y el nombre de un proyecto típico de Clojure. Mi primer proyecto sustancial de Clojure tenía declaraciones de espacio de nombres, y estructuras de carpetas correspondientes, como esta:
(ns com.14clouds.myapp.repository)
que infló mis referencias de funciones totalmente calificadas:
(com.14clouds.myapp.repository/load-by-name "foo")
Para complicar aún más las cosas, utilicé una estructura de directorio estándar de Maven :
|-- src/
| |-- main/
| | |-- java/
| | |-- clojure/
| | |-- resources/
| |-- test/
...
que es más compleja que la estructura "estándar" de Clojure de:
|-- src/
|-- test/
|-- resources/
que es el valor predeterminado de los proyectos de Leiningen y Clojure en sí.
Los mapas utilizan equals () de Java en lugar de Clojure = para la coincidencia de claves
Originalmente informado por Chouser en IRC , este uso de equals () de Java conduce a algunos resultados poco 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
Dado que las instancias Integer y Long de 1 se imprimen de la misma manera de forma predeterminada, puede ser difícil detectar por qué su mapa no devuelve ningún valor. Esto es especialmente cierto cuando pasa su clave a través de una función que, quizás sin que usted lo sepa, devuelve un long.
Cabe señalar que el uso de equals () de Java en lugar de = Clojure es esencial para que los mapas se ajusten a la interfaz java.util.Map.
Estoy usando Programming Clojure de Stuart Halloway, Practical Clojure de Luke VanderHart y la ayuda de innumerables piratas informáticos de Clojure en IRC y la lista de correo para ayudar con mis respuestas.