Redefiniendo una variable let'd en bucle Clojure
Pregunta
OK. He estado jugando con Clojure y yo continuamente topado con el mismo problema. Vamos a tomar este pequeño fragmento de código:
(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. Está claro que mi intención efecto secundario no está funcionando.
Entonces, ¿cómo se supone que voy a redefinir el valor de x en un circuito como este? Me doy cuenta de que esto no puede ser igual que Lisp (que podría utilizar una función anónima que recursivamente en él mismo, tal vez), pero si no averiguar cómo establecer la variable de este tipo, voy a ir loco.
Mi otra conjetura sería el uso de establecer !, pero eso da "destino de la asignación no válido", ya que no estoy en una forma de unión.
Por favor, me ilumine sobre cómo se supone que funciona.
Solución
def
define un nivel superior var, incluso si se utiliza en una función o un bucle interno de un cierto código. Lo que se obtiene en let
no son vars. Por la documentación para let
:
Los locales creados con let no son variables. Una vez creado sus valores nunca cambian!
No es necesario el estado mutable por su ejemplo aquí (El subrayado no es mío.); usted podría utilizar loop
y recur
.
(loop [x 128]
(when (> x 1)
(println x)
(recur (/ x 2))))
Si usted quiere ser de lujo podría evitar la loop
explícita por completo.
(let [xs (take-while #(> % 1) (iterate #(/ % 2) 128))]
(doseq [x xs] (println x)))
Si realmente querían utilizar el estado mutable, un átomo de podría funcionar .
(let [x (atom 128)]
(while (> @x 1)
(println @x)
(swap! x #(/ %1 2))))
(No es necesario un do
; while
envuelve su cuerpo en una explícita para usted.) Si realmente quería hacer esto con vars que tendría que hacer algo horrible como éste.
(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 Clojure idiomática en absoluto. Para utilizar con eficacia Clojure usted debe tratar de dejar de pensar en términos de estado mutable. Definitivamente le vuelven loco tratando de escribir código Clojure en un estilo no-funcional. Después de un tiempo puede que le resulte ser una agradable sorpresa lo poco que realmente necesitan las variables mutables.
Otros consejos
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
Entonces se puede escribir un bucle de la siguiente manera:
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 unidiomatic.
Si usted piensa que tiene variables locales mutables en funciones puras sería una herramienta de gran utilidad agradable que no hace ningún daño, ya que la función sigue siendo pura, que podría estar interesado en esta lista de correo de discusión donde Rich Hickey explica sus razones para la eliminación de ellos de la lengua. por qué no locales mutables?
Parte pertinente:
Si los locales fueron variables, es decir, mutable, a continuación, los cierres podría cerrar por encima estado mutable, y, dado que los cierres pueden escapar (sin un poco de sobrepeso prohibición en el mismo), el resultado sería hilo-inseguro. y las personas sin duda hacerlo, por ejemplo, a base de cierre pseudo-objetos. El resultado sería un enorme agujero en el enfoque de Clojure.
Sin mutables locales, las personas se ven obligadas a utilizar se repiten, una funcional bucle constructo. Si bien esto puede parecer extraño al principio, es tan sucinta como bucles con la mutación, y los patrones resultantes pueden ser reutilizado en Clojure en otro lugar, es decir, vuelve a producirse, reducir, alterar, conmutar etc son todos (lógicamente) muy similar. A pesar de que yo pudiera detectar y prevenir la mutación de los cierres de escape, he decidido mantener de esta manera por consistencia. Incluso en el contexto más pequeño, bucles no están mutando más fácil de entender y de depuración de los mutando. En cualquier caso, Vars están disponibles para su uso cuando sea apropiado.
La mayoría de los mensajes subsiguientes preocupaciones de ejecución una macro with-local-vars
;)
Se puede usar 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>