Question

OK. J'ai été bricoler avec Clojure et je lance sans cesse dans le même problème. Prenons ce petit fragment de code:

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

Maintenant, je pense que cela imprimer une séquence commençant par 128 comme ceci:

128
64
32
16
8
4
2

Au lieu de cela, il est une boucle infinie, l'impression 128 reprises. Il est clair que mon effet secondaire prévu ne fonctionne pas.

Alors, comment suis-je censé redéfinir la valeur de x dans une boucle comme ça? Je sais que cela ne peut pas être Lisp comme (je pouvais utiliser une fonction anonyme qui récursif sur ce soi-même, peut-être), mais si je ne pas comprendre comment définir comme variables, je vais devenir fou.

Mon autre hypothèse serait d'utiliser set !, mais qui donne « la cible d'affectation non valide », puisque je ne suis pas sous une forme de liaison.

S'il vous plaît, me éclairer sur la façon dont cela est censé fonctionner.

Était-ce utile?

La solution

def définit un var toplevel, même si vous l'utilisez dans une fonction ou boucle interne de certains code. Ce que vous obtenez en let ne sont pas vars. Par la documentation let :

  

Les sections locales créées avec let ne sont pas des variables. Une fois créé leurs valeurs ne changent jamais!

Vous n'avez pas besoin état mutable pour votre exemple ici (Emphasis pas le mien.); vous pouvez utiliser loop et recur.

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

Si vous voulez être de fantaisie, vous pouvez éviter complètement l'loop explicite.

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

Si vous vraiment voulait utiliser l'état mutable, atome pourrait fonctionner .

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

(Vous n'avez pas besoin do, while enveloppe son corps dans un un explicite pour vous.) Si vous vraiment, vraiment voulait faire avec vars que vous auriez à faire quelque chose d'horrible comme ça.

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

Mais ce qui est très laid et ce n'est pas idiomatiques Clojure du tout. Pour utiliser efficacement Clojure vous devriez essayer d'arrêter de penser en termes d'état mutable. Il vous certainement rendre fou en essayant d'écrire du code Clojure dans un style non fonctionnel. Après un certain temps, vous trouverez peut-être une agréable surprise combien il est rare que vous avez réellement besoin des variables mutables.

Autres conseils

Vars (c'est ce que vous obtenez lorsque vous « def » quelque chose) ne sont pas destinés à être réaffecté (mais peut être):

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

Il n'y a rien qui vous empêche de le faire:

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

Si vous voulez un thread local réglable « lieu » vous pouvez utiliser « lier » et « 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

Alors vous pouvez écrire une boucle comme ceci:

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

Mais je pense que cela est assez unidiomatic.

Si vous pensez que d'avoir des variables locales mutables dans les fonctions pures serait une caractéristique bien pratique qui ne fait pas de mal, parce que la fonction reste pur, vous pourriez être intéressé par cette discussion de la liste de diffusion où Rich Hickey explique ses raisons pour les enlever de la langue. Pourquoi pas les habitants mutables?

Partie pertinente:

  

Si les habitants étaient des variables, à savoir mutable, puis les fermetures pourraient fermer plus   état mutable, et, étant donné que les fermetures peuvent échapper (sans quelques extra   interdiction de même), le résultat serait fil dangereux. Et les gens   certainement le faire, par exemple pseudo-objets sur la base de fermeture. Le résultat   serait un énorme trou dans l'approche de Clojure.

     

Sans les habitants mutables, les gens sont obligés d'utiliser RECUR, fonctionnel   construction en boucle. Bien que cela puisse sembler étrange au premier abord, il est tout aussi   succincts que des boucles avec mutation, et les motifs peuvent être obtenus   réutilisé ailleurs dans Clojure, à savoir se reproduire, réduire, modifier, etc navette   sont tous (logiquement) très similaire. Même si je pouvais détecter et   empêcher les fermetures d'échapper à muter, j'ai décidé de garder cette façon   pour la cohérence. Même dans le contexte le plus petit, les boucles non sont muter   plus facile à comprendre et à déboguer que les muter. Dans tous les cas, Vars   sont disponibles pour le cas échéant.

La majorité des messages ultérieurs concerne la mise en œuvre d'une macro with-local-vars;)

Vous pouvez plus utiliser à la place idiomatiquement iterate et take-while,

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

(128 64 32 16 8 4 2)
user>
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top