2012-02-06 14 views
37

Estoy tratando de entender qué hace ^:const en clojure. Esto es lo que dicen los desarrolladores. http://dev.clojure.org/display/doc/1.3¿Cómo funciona Clojure ^: const?

(def constantes de {: pi 3,14 : e 2,71}) ​​

(def ^: pi const (: constantes pi)) (def ^: const e (: e constantes))

La sobrecarga de mirar hacia arriba: ey: pi en el mapa ocurre en tiempo de compilación, como (: constantes pi) y (constantes e) se evalúan cuando se evalúan sus formularios padre def.

Esto es confuso ya que los metadatos es para el var obligado a pi símbolo, y la var obligado a e símbolo, sin embargo, la siguiente oración dice que ayuda a acelerar las búsquedas de mapa, no las Búsquedas de var.

¿Alguien puede explicar lo que ^:const está haciendo y la razón detrás de usarlo? ¿Cómo se compara esto con el uso de un bloque gigante let o usando una macro como (pi) y (e)?

Respuesta

56

Eso me parece un mal ejemplo, ya que las cosas sobre la búsqueda de mapas solo confunden el problema.

Un ejemplo más realista sería:

(def pi 3.14) 
(defn circumference [r] (* 2 pi r)) 

En este caso, el cuerpo de la circunferencia se compila en código que elimina referencia pi en tiempo de ejecución (llamando Var.getRawRoot), cada circunferencia tiempo se llama.

(def ^:const pi 3.14) 
(defn circumference [r] (* 2 pi r)) 

En este caso, la circunferencia se compila en exactamente el mismo código como si hubiera sido escrito así:

(defn circumference [r] (* 2 3.14 r)) 

Es decir, la llamada a Var.getRawRoot se omite, lo que ahorra una un poco de tiempo Aquí es una medición rápida, donde circ es la primera versión anteriormente, y CIRC2 es la segunda:

user> (time (dotimes [_ 1e5] (circ 1))) 
"Elapsed time: 16.864154 msecs" 
user> (time (dotimes [_ 1e5] (circ2 1))) 
"Elapsed time: 6.854782 msecs" 
+0

Esto significa que el siguiente (def ^: const key-to-num {: one 1: two 2}) (def sum (+ (: one key-to-num) (: dos key-to- num)) se compila a (def resumir (+ 1 2)) –

+0

no, ^:? const sólo funciona en los valores primitivos, no objetos arbitrarios – noisesmith

9

En los documentos de ejemplo, intentan mostrar que en la mayoría de los casos si def una var es el resultado de buscar algo en un mapa sin usar const, la búsqueda se realizará cuando la clase se cargue. por lo que paga el costo una vez cada vez que ejecuta el programa (no en cada búsqueda, solo cuando se carga la clase). Y pagar el costo de buscar el valor en la var cada vez que se lee.

Si por el contrario lo hace const entonces el compilador preformas de las operaciones de búsqueda en tiempo de compilación y luego emiten una última variable sencilla Java y tendrá que pagar el costo total de las operaciones de búsqueda sólo una vez en el momento de compilar el programa.

Este es un ejemplo artificial porque una consulta de un mapa en tiempo de carga de clases y algunas operaciones de búsqueda var en tiempo de ejecución son básicamente nada, aunque ilustra el punto de que algunos trabajos que sucede en tiempo de compilación, algunos en tiempo de carga, y el resto también. .. el resto del tiempo

+2

No creo que esto sea preciso. El ahorro más importante no es la búsqueda de mapa, sino la búsqueda de Var para 'pi' y' e', que ocurriría cada vez que haga referencia a cualquiera de ellos si carece de '^: const', pero no ocurre en todo cuando '^: const' está incluido. – amalloy

+0

Gracias por señalar eso, he editado para agregar la búsqueda var también. –

8

Además del aspecto eficiencia se ha descrito anteriormente, hay un aspecto de seguridad que también es útil.Considere el siguiente código:

(def two 2) 
(defn times2 [x] (* two x)) 
(assert (= 4 (times2 2))) ; Expected result 

(def two 3)     ; Ooops! The value of the "constant" changed 
(assert (= 6 (times2 2))) ; Used the new (incorrect) value 

(def ^:const const-two 2) 
(defn times2 [x] (* const-two x)) 
(assert (= 4 (times2 2))) ; Still works 

(def const-two 3)   ; No effect! 
(assert (= 3 const-two)) ; It did change... 
(assert (= 4 (times2 2))) ; ...but the function did not. 

Así, mediante el uso de la ^: const en la definición de metadatos VARs, los VARs son efectivamente "inline" por todos los lugares que se utilizan. Cualquier cambio posterior en la var, por lo tanto, no afecta a ningún código donde el valor "anterior" ya haya sido inlineado.

El uso de ^: const también sirve para una función de documentación. Cuando uno lee (def ^: const pi 3.14159) le dice al lector que la var pi no tiene la intención de cambiar, que es simplemente un nombre conveniente (& con suerte descriptivo) para el valor 3.14159.

Dicho lo anterior, tenga en cuenta que nunca uso ^:const en mi código, ya que es engañoso y proporciona "falsa seguridad" de que una var nunca cambiará. El problema es que ^:const implica que no se puede redefinir una var, pero como vimos con const-two, no impide que se cambie la var. En su lugar, ^:const oculta el hecho de que la var tiene un nuevo valor, ya que const-two ha sido copiado/en línea (en tiempo de compilación) a cada lugar de uso antes de que se cambie la var (en tiempo de ejecución).

Una solución mucho mejor sería arrojar una excepción al intentar cambiar una var ^:const.

+1

[La Guía de Estilo Clojure] (https: // github. com/bbatsov/clojure-style-guide # naming) dice "No use una notación especial para las constantes; todo se supone constante a menos que se especifique lo contrario." ¿Eso sugiere que los valores están rutinariamente en línea, al menos cuando no son re -'def'ed? (Tal vez '^: const' signifique solamente" podría alinearse incluso si hay una definición posterior 'de esta variable ", pero si no hay una definición posterior', incluso no-^ ^: const'ed vars mig ht obtener inlined? Mi muy loca suposición.) O si '^: const'ed vars son diferentes, ¿por qué no debería hacerlo explícito nombrando, por ej. '+ nombre +' como en Common Lisp. – Mars

+0

Usando ^: const tiene 2 efectos. (1) es que el compilador alineará el valor, y no usará la var por lo que eres inmune a los cambios o al abuso de la var. (2) es simplemente documentación; el autor afirma que el valor * debe * nunca cambiar. –

+0

@Mars Creo que la guía de estilo significa que Vars se debe suponer constante, ya que en Clojure, se desaconseja cambiar el valor raíz de un Var después de que se configuró. Un Var que cambia, normalmente sería un Var dinámico y estaría rodeado de orejeras. De modo que 'name' se supondría constante (ya que nunca cambiará, no como en la optimización o garantía del compilador), y se supondría que' * name * 'no es constante. Mientras que '^: const' es solo para indicar al compilador que debe optimizar el código al delimitar el uso de la var. –

Cuestiones relacionadas