2012-07-30 16 views
24

traté de entender las variables dinámicas y función de unión, así que probamos este (clojure 1.3):clojure y ^: dinámico

user=> (defn f [] 
      (def ^:dynamic x 5) 
      (defn g [] (println x)) 
      (defn h [] (binding [x 3] (g))) 
      (h)) 
#'user/f 
user=> (f)  
5 
nil 

confundido, me trataron este código algo más simple:

user=> (def ^:dynamic y 5) 
#'user/y 
user=> (defn g [] (println y)) 
#'user/g 
user=> (defn h [] (binding [y 3] (g))) 
#'user/h 
user=> (h) 
3 
nil 

Lo Cuál es la diferencia entre las dos piezas de código? ¿Por qué el segundo ejemplo funciona pero el primero no?

Consejo: Me acabo de dar cuenta que las siguientes obras (aún no entienden completamente por qué):

user=> (def ^:dynamic y 5) 
#'user/y 
user=> (defn f [] (defn g [] (println y)) (defn h [] (binding [y 3] (g))) (h)) 
#'user/f 
user=> (f) 
3 
nil 
user=> 

Respuesta

26

consigo 3 como resultado (como era de esperar) cuando corro el primer ejemplo en Clojure 1.4 .... ¿has probado esto con un REPL nuevo?

^:dynamic es una instrucción para el compilador Clojure de que un símbolo (como se define con def) está destinado a rebote dinámico (con binding).

Ejemplo:

(def foo 1) 
(binding [foo 2] foo) 
=> IllegalStateException Can't dynamically bind non-dynamic var: ... 

(def ^:dynamic bar 10) 
(binding [bar 20] bar) ;; dynamically bind bar within the scope of the binding 
=> 20 
bar      ;; check underlying value of bar (outside the binding) 
=> 10 

Nota que binding tiene un alcance dinámico dentro del subproceso de llamada - cualquier funciones llamadas dentro de la unión verá el valor modificado de bar (20), pero cualquier otros hilos todavía verá el sin cambios valor de la raíz de 10.

Por último, un par de puntos de estilo que pueden serle útiles:

  • Por lo general es C onsidered mala idea para poner def y defn dentro de las funciones, ya que afectan el espacio de nombres adjunto. Dentro de las funciones, debe usar (let [foo bar] ...) en su lugar.
  • Cuando desee utilizar binding, normalmente debería considerar si puede lograr el mismo resultado utilizando funciones de orden superior. binding es útil en algunos contextos, pero en general no es una buena forma de pasar parámetros: la composición de funciones suele ser mejor a largo plazo. La razón para esto es que binding crea un contexto implícito que se requiere para la ejecución de su función y esto puede ser difícil de probar/depurar.
+0

Entiendo pros/contras de encuadernación. También me doy cuenta de que el primer ejemplo de código es un código clojure inusual. Lo que no entendí es por qué no funcionó (con 1.3, nueva respuesta). – Kevin

+0

¡Tengo problemas para ver cuándo querrías encuadernar! Parece anatema a la manera funcional. Qué me estoy perdiendo ? – Hendekagon

+1

@Hendekagon - probablemente merece una pregunta ASÍ por derecho propio. Pero lo encontré útil como una forma adicional de pasar el contexto mientras depuraba/trabajaba en REPL. Si lo hiciera de manera puramente funcional, necesitaría enhebrar nuevos parámetros durante una llamada (potencialmente muy larga) grafico. – mikera