¿Cómo funciona Clojure ^: const?
constants (3)
Además del aspecto de eficiencia 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.
Por lo tanto, al utilizar los metadatos ^: const al definir vars, los vars están efectivamente "en línea" en cada lugar donde se usan. Cualquier cambio posterior en la var, por lo tanto, no afecta ningún código donde el valor "antiguo" ya haya sido insertado.
El uso de ^: const también cumple una función de documentación. Cuando uno lee (def ^: const pi 3.14159) se le dice al lector que la var pi no está destinada a cambiar, que es simplemente un nombre conveniente (y con suerte descriptivo) para el valor 3.14159.
Habiendo dicho lo anterior, tenga en cuenta que nunca uso ^:const
en mi código, ya que es engañoso y proporciona una "falsa seguridad" de que una var nunca cambiará. El problema es que ^:const
implica que uno no 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 var tiene un nuevo valor, ya que const-two
se ha copiado / en línea (en tiempo de compilación) en cada lugar de uso antes de que se cambie la var (en tiempo de ejecución).
Una solución mucho mejor sería lanzar una excepción al intentar cambiar una ^:const
var.
Estoy tratando de entender lo que ^:const
hace en clojure. Esto es lo que dicen los dev devs. http://dev.clojure.org/display/doc/1.3
(def constantes {: pi 3.14: e 2.71})
(def ^: const pi (: constantes pi)) (def ^: const e (: constantes e))
La sobrecarga de buscar: e y: pi en el mapa ocurre en tiempo de compilación, ya que (: constantes de pi) y (: constantes de e) se evalúan cuando se evalúan sus formas de definición padre.
Esto es confuso ya que los metadatos son para la var enlazada al símbolo pi
y la var ligada al símbolo e
, sin embargo, la siguiente oración dice que ayuda a acelerar las búsquedas en el mapa, no las búsquedas var.
¿Alguien puede explicar lo que hace ^:const
y la razón detrás de usarlo? ¿Cómo se compara esto con usar un bloque let
o usar una macro como (pi)
y (e)
?
En los documentos de ejemplo, están tratando de mostrar que en la mayoría de los casos, si def
una var como resultado de buscar algo en un mapa sin usar const, la búsqueda se realizará cuando se cargue la clase. así que paga el costo una vez cada vez que ejecuta el programa (no en cada búsqueda, solo cuando se carga la clase). Y pague el costo de buscar el valor en la var cada vez que se lea.
Si, por el contrario, lo hace constante, el compilador realizará la búsqueda en el momento de la compilación y luego emitirá una variable final java simple y pagará el costo de la búsqueda solo una vez en total en el momento de compilar el programa.
Este es un ejemplo artificial porque una búsqueda en el mapa en el tiempo de carga de la clase y algunas búsquedas en el tiempo de ejecución de la clase no son básicamente nada, aunque ilustra el punto de que algo de trabajo ocurre en el momento de la compilación, otro en el tiempo de la carga y el resto bien ... el resto del tiempo
Me parece que es un mal ejemplo, ya que el tema de la búsqueda en el mapa simplemente confunde 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 un código que elimina las referencias pi en el tiempo de ejecución (llamando a Var.getRawRoot), cada vez que se llama a la circunferencia.
(def ^:const pi 3.14)
(defn circumference [r] (* 2 pi r))
En este caso, la circunferencia se compila exactamente en el mismo código que si se hubiera escrito así:
(defn circumference [r] (* 2 3.14 r))
Es decir, se omite la llamada a Var.getRawRoot, lo que ahorra un poco de tiempo. Aquí hay una medición rápida, donde circ es la primera versión anterior 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"