Neudefinieren eine let'd Variable in Clojure loop
Frage
OK. Ich habe mit Clojure bastelt und ich laufe ständig in das gleiche Problem. Lassen Sie sich dieses kleine Fragment von Code übernehmen:
(let [x 128]
(while (> x 1)
(do
(println x)
(def x (/ x 2)))))
Jetzt erwarte ich, wie mit 128 Starten einer Sequenz auszudrucken so:
128
64
32
16
8
4
2
Stattdessen ist es eine Endlosschleife, Druck 128 über und über. Offensichtlich meine beabsichtigte Nebenwirkung nicht funktioniert.
Wie soll ich den Wert von x in einer Schleife wie folgt neu zu definieren? Ich weiß, das kann nicht wie Lisp wird (ich könnte eine anonyme Funktion verwenden, die recurses auf es selbst ist, vielleicht), aber wenn ich herausfinden, nicht, wie variabel einstellen wie diese, ich bin verrückt gehen wird.
Meine andere Vermutung wäre, gesetzt zu verwenden !, aber das gibt „Ungültige Zuordnung Ziel“, da ich nicht in einer verbindlichen Form bin.
Bitte, erleuchte mich, wie dies funktionieren soll.
Lösung
def
definiert einen Toplevel var, auch wenn Sie es in einer Funktion oder innere Schleife von Code verwenden. Was Sie in let
bekommen sind nicht Vars. Per in der Dokumentation zu let
:
Die Einheimischen let erstellt sind keine Variablen. Sobald ihre Werte erstellt nie ändern!
Sie brauchen nicht veränderbaren Zustand für Ihr Beispiel hier (Schwerpunkt nicht meint.); Sie loop
und recur
nutzen könnten.
(loop [x 128]
(when (> x 1)
(println x)
(recur (/ x 2))))
Wenn Sie wollten Lust, Ihnen die explizite loop
ganz vermeiden können.
(let [xs (take-while #(> % 1) (iterate #(/ % 2) 128))]
(doseq [x xs] (println x)))
Wenn Sie wirklich wollte wandelbaren Zustand verwenden, ein Atom funktionieren könnte .
(let [x (atom 128)]
(while (> @x 1)
(println @x)
(swap! x #(/ %1 2))))
(Sie brauchen keinen do
; while
hüllt seinen Körper in einem expliziten für Sie.) Wenn Sie wirklich, wirklich wollte, dies zu tun mit vars etwas schreckliches wie dies zu tun haben würde.
(with-local-vars [x 128]
(while (> (var-get x) 1)
(println (var-get x))
(var-set x (/ (var-get x) 2))))
Das ist aber sehr hässlich und es ist nicht idiomatische Clojure alle an. So verwenden Sie Clojure effektiv sollten Sie versuchen, in Bezug auf die wandelbaren Zustand aufhören zu denken. Es wird auf jeden Fall fahren verrückt versuchen, Clojure Code in einem nicht-funktionalen Stil zu schreiben. Nach einer Weile können Sie es finden eine angenehme Überraschung zu sein, wie selten tatsächlich Sie änderbare Variablen benötigen.
Andere Tipps
Vars (das ist, was Sie bekommen, wenn Sie „def“ etwas) nicht neu zugewiesen werden soll (kann aber sein):
user=> (def k 1)
#'user/k
user=> k
1
Es gibt nichts, was Sie zu tun zu stoppen:
user=> (def k 2)
#'user/k
user=> k
2
Wenn Sie einen Thread-local einstellbaren „Ort“ wollen, können Sie „Bindung“ und „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
So können Sie eine Schleife wie folgt schreiben:
user=> (binding [j 0]
(while (< j 10)
(println j)
(set! j (inc j))))
0
1
2
3
4
5
6
7
8
9
nil
Aber ich denke, das ist ziemlich unidiomatic ist.
Wenn Sie denken, dass in reinen Funktionen wandelbar lokale Variablen, die wäre eine schöne praktische Funktion, die nicht schadet, weil die Funktion noch rein bleibt, könnten Sie in dieser Mailingliste Diskussion interessiert, wo Reich Hickey seine Gründe erläutert, für sie zu entfernen von der Sprache. Warum nicht wandelbar Einheimischen?
Relevante Teil:
Wenn Einheimischen waren Variablen, das heißt wandelbar, dann könnte Verschlüsse schließen über wandelbarer Zustand und in Anbetracht, dass Verschlüsse (ohne etwas mehr entweichen können Verbot der gleichen), wäre das Ergebnis fad unsicher. Und Leute sicherlich tun würde, z.B. Verschluss-basierten Pseudo-Objekte. Das Ergebnis ein riesiges Loch in Clojure Ansatz wäre.
Ohne wandelbar Einheimischen sind die Menschen gezwungen, erneut auftreten zu verwenden, ist ein Funktions Schleifenkonstrukt. Während dies zunächst seltsam erscheinen mag, ist es genauso prägnanter als Schleifen mit der Mutation und die sich ergebenden Muster sein kann an anderer Stelle in Clojure, das heißt wieder auftreten, verringern, verändern, pendeln usw. wiederverwendet sehr ähnlich sind (logisch). Obwohl ich konnte erkennen und verhindern mutiert Verschlüsse an der Flucht, habe ich beschlossen, es auf diese Weise zu halten für Konsistenz. Selbst im kleinsten Zusammenhang nicht-mutierende Schleifen leichter zu verstehen und zu debuggen als diejenigen mutiert. In jedem Fall Vars ist für den Einsatz bei Bedarf.
Die Mehrheit der nachfolgenden Beiträge betrifft ein with-local-vars
Makro implementiert;)
Sie mehr idiomatisch iterate
und take-while
stattdessen verwenden könnte,
user> (->> 128
(iterate #(/ % 2))
(take-while (partial < 1)))
(128 64 32 16 8 4 2)
user>