2009-06-02 19 views
35

OK. He estado jugando con Clojure y continuamente me encuentro con el mismo problema. Vamos a tomar este pequeño fragmento de código:Redefinición de una variable let en dloj Clojure

(let [x 128] 
    (while (> x 1) 
    (do 
     (println x) 
     (def x (/ x 2))))) 

Ahora me esperaba esto para imprimir una secuencia que comienza con 128, como así:

128 
64 
32 
16 
8 
4 
2 

En cambio, es un bucle infinito, la impresión de 128 y otra vez. Claramente, mi efecto secundario previsto no está funcionando.

Entonces, ¿cómo se supone que voy a redefinir el valor de x en un bucle como este? Me doy cuenta de que esto puede no ser como Lisp (podría usar una función anónima que recursivamente en sí mismo, tal vez), pero si no descubro cómo configurar una variable como esta, me voy a volver loco.

Mi otra suposición sería usar set !, pero eso da "objetivo de asignación inválido", ya que no estoy en una forma vinculante.

Por favor, infórmeme sobre cómo se supone que esto funciona.

Respuesta

48

def define una var a nivel, incluso si la usa en una función o bucle interno de algún código. Lo que obtienes en let no son vars. Por the documentation for let:

Los locales creados con let no son variables. ¡Una vez creados, sus valores nunca cambian!

(El énfasis no es mío) No necesita el estado mutable para su ejemplo aquí; puede usar loop y recur.

(loop [x 128] 
    (when (> x 1) 
    (println x) 
    (recur (/ x 2)))) 

Si quería ser de lujo podría evitar la explícita loop por completo.

(let [xs (take-while #(> % 1) (iterate #(/ % 2) 128))] 
    (doseq [x xs] (println x))) 

Si realmente quería usar estado mutable, un atom podría funcionar.

(let [x (atom 128)] 
    (while (> @x 1) 
    (println @x) 
    (swap! x #(/ %1 2)))) 

(que no es necesario un do; while envuelve su cuerpo en una explícita para usted.) Si muy, muy quería hacer esto con vars que tendría que hacer algo horrible como esta.

(with-local-vars [x 128] 
    (while (> (var-get x) 1) 
    (println (var-get x)) 
    (var-set x (/ (var-get x) 2)))) 

Pero esto es muy feo y no es idiomática Clojure en absoluto. Para usar Clojure de manera efectiva, debe intentar dejar de pensar en términos de estado mutable. Definitivamente te volverá loco tratando de escribir el código Clojure en un estilo no funcional. Después de un tiempo, puede resultarle una agradable sorpresa que pocas veces necesite variables mutables.

+1

Gracias. Me doy cuenta de que mi camino no era Lispy, ya que los efectos secundarios están mal visto. Estaba pirateando algo (un problema de Project Euler) y no pude hacer funcionar ese sencillo caso de prueba, demostrando que no entendía nada. Gracias por la ayuda. Me olvidé de que el bucle podría ser recurrente, que funciona de manera muy limpia (sin la función adicional haciendo recursividad). – MBCook

+4

Los efectos secundarios son Lispy dependiendo de qué Lisp estés viendo. En Common Lisp te saldrías con (loop para x = 128 then (/ x 2) while (> x 1) do (print x)). Pero los efectos secundarios no son Clojurish. –

+1

Es muy viejo Pero esta es una muy buena respuesta, soy nuevo en Clojure, esto me salvó de horas de luchar con el mismo problema. Muchas gracias @BrianCarper – shan

12

Vars (eso es lo que se obtiene cuando se "def" algo) no están destinados a ser reasignado (pero puede ser):

user=> (def k 1) 
#'user/k 
user=> k 
1 

No hay nada que nos impida hacerlo:

user=> (def k 2) 
#'user/k 
user=> k 
2 

Si quieres un ajustable "lugar" local de subprocesos puede utilizar "unión" y "set":

user=> (def j) ; this var is still unbound (no value) 
#'user/j 
user=> j 
java.lang.IllegalStateException: Var user/j is unbound. (NO_SOURCE_FILE:0) 
user=> (binding [j 0] j) 
0 

Así entonces se puede wr ite un ciclo como este:

user=> (binding [j 0] 
     (while (< j 10) 
      (println j) 
      (set! j (inc j)))) 
0 
1 
2 
3 
4 
5 
6 
7 
8 
9 
nil 

Pero creo que esto es bastante unidiomático.

5

Si cree que tener variables locales mutables en funciones puras sería una buena característica conveniente que no daña, porque la función sigue siendo pura, puede que le interese esta discusión de la lista de correo donde Rich Hickey explica sus razones para eliminar ellos del lenguaje. Why not mutable locals?

parte pertinente:

Si los locales fueron variables, es decir, mutable, a continuación, los cierres podrían cerrar sobre estado mutable, y, dado que los cierres pueden escapar (sin algún prohibición adicional sobre la misma), el resultado Sería inseguro. Y la gente ciertamente lo haría, p. pseudoobjetos basados ​​en cierre. El resultado sería un gran agujero en el enfoque de Clojure.

Sin locales mutables, las personas están obligadas a recurrir, una construcción de bucle recurrente. Si bien esto puede parecer extraño al principio, es tan sucinto como bucles con mutación, y los patrones resultantes pueden ser reutilizados en cualquier otro lugar de Clojure, es decir, recurrir, reducir, alterar, conmutar etc. son todos (lógicamente) muy similares. Aunque pude detectar y evitar que los cierres mutantes escapen, decidí mantenerlo así para mantener la coherencia. Incluso en el contexto más pequeño, los bucles no mutantes son más fáciles de comprender y depurar que los mutantes. En cualquier caso, Vars están disponibles para su uso cuando corresponda.

La mayoría de los mensajes subsiguientes preocupaciones de ejecución de una macro with-local-vars;)

1

Usted podría utilizar más idiomáticamente iterate y take-while lugar,

user> (->> 128 
      (iterate #(/ % 2)) 
      (take-while (partial < 1))) 

(128 64 32 16 8 4 2) 
user>