¿Cuándo usar un Var en lugar de una función?

Estoy repasando el libro de desarrollo web de clojure y me dice que pase el objeto var de controlador (definido a continuación) en lugar de la función en sí porque la función cambiará dinámicamente (esto es lo que hace el ajuste y recarga).

El libro dice:

“Tenga en cuenta que tenemos que crear una var del controlador para que este middleware funcione. Esto es necesario para garantizar que se devuelva el objeto Var que contiene la función del controlador actual. Si usáramos el controlador en su lugar, la aplicación solo vería el el valor original de la función y los cambios no se reflejarán “. Realmente no entiendo lo que esto significa, ¿los vars son similares a los punteros c?

(ns ring-app.core (:require [ring.adapter.jetty :as jetty] [ring.util.response :as response] [ring.middleware.reload :refer [wrap-reload]])) (defn handler [request] (response/response (str "/ your IP is: " (:remote-addr request) ""))) (defn wrap-nocache [handler] (fn [request] (-> request handler (assoc-in [:headers "Pragma"] "no-cache")))) 

Aquí está la llamada del controlador:

 (defn -main [] (jetty/run-jetty (wrap-reload (wrap-nocache (var handler))) {:port 3001 :join? false})) 

Sí, la var es similar a un puntero C Esto está mal documentado.

Supongamos que defines fred siguiente manera:

 (defn fred [x] (+ x 1)) 

En realidad, hay 3 cosas aquí. En primer lugar, fred es un símbolo. Hay una diferencia entre un símbolo fred (sin comillas) y la palabra clave :fred (marcado por el líder : char) y la cadena "fred" (marcado por una comilla doble en ambos extremos). Para Clojure, cada uno de ellos está compuesto por 4 personajes; es decir, ni los dos puntos de la palabra clave ni las comillas dobles de la cadena se incluyen en su longitud o composición:

 > (name 'fred) "fred" > (name :fred) "fred" > (name "fred") "fred" 

La única diferencia es cómo se interpretan. Una cadena está destinada a representar datos de usuario de cualquier tipo. Una palabra clave está destinada a representar información de control para el progtwig, en una forma legible (en lugar de “números mágicos” como 1 = izquierda, 2 = derecha, solo usamos palabras clave :left y :right .

Un símbolo está destinado a señalar cosas, como en Java o C. Si decimos

 (let [x 1 y (+ x 1) ] (println y)) ;=> 2 

luego x apunta al valor 1, y apunta al valor 2, y vemos el resultado impreso.

la forma (def ...) introduce un tercer elemento invisible , la var . Entonces si decimos

 (def wilma 3) 

ahora tenemos 3 objetos para considerar. wilma es un símbolo, que apunta a una var , que a su vez apunta al valor 3 . Cuando nuestro progtwig encuentra el símbolo wilma , se evalúa para encontrar la var . Del mismo modo, la var se evalúa para obtener el valor 3. Por lo tanto, es como un indirecto de 2 niveles de punteros en C. Como tanto el símbolo como la var son “autoevaluados”, esto sucede de forma automática e invisible y no tiene que pensar en la var (de hecho, la mayoría de la gente no está realmente consciente de que el paso intermedio invisible existe).

Para nuestra función anterior, existe una situación similar, excepto que var apunta a la función anónima (fn [x] (+ x 1)) lugar del valor 3 como wilma .

Podemos “cortocircuitar” la autoevaluación de la var como:

 > (var wilma) #'clj.core/wilma 

o

 > #'wilma #'clj.core/wilma 

donde el lector macro #' (pound-quote) es una manera abreviada de llamar a la forma especial (var ...) . Tenga en cuenta que una forma especial como var es un comstackdor integrado como ‘if’ o ‘def’, y no es lo mismo que una función normal. La forma especial var devuelve el objeto var adjunto al símbolo wilma . El clojure REPL imprime el objeto var usando la misma abreviatura, por lo que ambos resultados se ven iguales.

Una vez que tenemos el objeto var, la autoevaluación está deshabilitada:

 > (println (var wilma)) #'clj.core/wilma 

Si queremos llegar al valor que señala wilma , necesitamos usar var-get :

 > (var-get (var wilma)) 3 > (var-get #'wilma) 3 

Lo mismo funciona para Fred:

 > (var-get #'fred) #object[clj.core$fred 0x599adf07 "clj.core$fred@599adf07"] > (var-get (var fred)) #object[clj.core$fred 0x599adf07 "clj.core$fred@599adf07"] 

donde el elemento #object[clj.core$fred ...] es la forma en Clojure de representar un objeto de función como una cadena.

Con respecto al servidor web, ¿puede decir a través de la var? función o no, si el valor suministrado es la función del manejador o la var que apunta a la función del manejador.

Si escribe algo como:

 (jetty/run-jetty handler) 

la doble autoevaluación arrojará el objeto de función del manejador, que se pasa a run-jetty . Si, en cambio, escribe:

 (jetty/run-jetty (var handler)) 

a continuación, la var que apunta al objeto de función del manejador se pasará a run-jetty . Entonces, run-jetty tendrá que usar una sentencia if o equivalente para determinar qué ha recibido, y llamar (var-get ...) si ha recibido una var lugar de una función. Por lo tanto, cada vez que pase (var-get ...) devolverá el objeto al que señala actualmente la var . Entonces, la var actúa como un puntero global en C, o una variable de “referencia” global en Java.

Si transfiere un objeto de función a run-jetty , guarda un “puntero local” al objeto de función y no hay manera de que el mundo exterior cambie a lo que se refiere el puntero local.

Puede encontrar más detalles aquí:

Con suerte, este pequeño ejemplo lo llevará por buen camino:

 > (defn your-handler [x] x) #'your-handler > (defn wrap-inc [f] (fn [x] (inc (fx)))) > #'wrap-inc > (def your-app-with-var (wrap-inc #'your-handler)) #'your-app-with-var > (def your-app-without-var (wrap-inc your-handler)) #'your-app-without-var > (your-app-with-var 1) 2 > (your-app-without-var 1) 2 > (defn your-handler [x] 10) #'your-handler > (your-app-with-var 1) 11 > (your-app-without-var 1) 2 

La intuición para esto es cuando se usa una var cuando se crea el controlador, se pasa un “contenedor” con algún valor, cuyo contenido se puede cambiar en el futuro definiendo var con el mismo nombre. Cuando no utiliza var (como en your-app-without-var ) está pasando un valor actual de este “contenedor”, que no se puede redefinir de ninguna manera.

Ya hay un par de buenas respuestas. Solo quería agregar esta advertencia:

 (defn f [] 10) (defn g [] (f)) (g) ;;=> 10 (defn f [] 11) ;; -Dclojure.compiler.direct-linking=true (g) ;;=> 10 ;; -Dclojure.compiler.direct-linking=false (g) ;;=> 11 

Entonces, cuando la vinculación directa está activada, la indirección a través de una var es reemplazada por una invocación estática directa. Similar a la situación con el controlador, pero luego con cada invocación de var, a menos que se refiera explícitamente a una var, como:

 (defn g [] (#'f))