2009-06-02 7 views
35

OK. Ich habe mit Clojure herumgebastelt und stoße ständig auf das gleiche Problem. Lassen Sie uns dieses kleine Codefragment nehmen:Neudefinieren einer Let'd-Variablen in Clojure-Schleife

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

Jetzt habe ich dies erwarten wie so mit 128 Starten einer Sequenz auszudrucken:

128 
64 
32 
16 
8 
4 
2 

Stattdessen ist es eine Endlosschleife, Druck 128 über und über. Offensichtlich funktioniert meine beabsichtigte Nebenwirkung nicht.

Also, wie soll ich den Wert von x in einer Schleife wie dieser neu definieren? Mir ist klar, dass das nicht Lisp sein mag (ich könnte eine anonyme Funktion verwenden, die vielleicht auf sich selbst zurückgeht), aber wenn ich nicht herausfinde, wie ich Variablen so einstellen kann, werde ich verrückt werden.

Meine andere Vermutung wäre, gesetzt zu verwenden !, aber das gibt „Ungültige Zuordnung Ziel“, da ich nicht in einer verbindlichen Form bin.

Bitte erleuchten Sie mich, wie dies funktionieren soll.

Antwort

48

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. Pro the documentation for let:

Mit let erstellte Locals sind keine Variablen. Einmal geschaffen, ändern sich ihre Werte nie!

(Hervorhebung nicht meins.) Sie brauchen nicht mutierbaren Zustand für Ihr Beispiel hier; Sie könnten loop und recur verwenden.

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

Wenn Sie sein Phantasie wollte man die explizite loop ganz vermeiden konnte.

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

Wenn Sie wirklich wandelbaren Zustand verwenden wollen, könnte ein atom arbeiten.

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

(Sie brauchen kein do; while hüllt seinen Körper in einem expliziten für Sie.) Wenn Sie wirklich, wirklich dies mit vars tun wollte würden Sie etwas Schreckliches zu tun haben, wie Dies.

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

Aber das ist sehr hässlich und es ist nicht idiomatische Clojure überhaupt. Um Clojure effektiv zu verwenden, sollten Sie versuchen, das Denken in einem veränderlichen Zustand zu stoppen. Es wird Sie definitiv verrückt machen, wenn Sie versuchen, Clojure-Code in einem nicht-funktionalen Stil zu schreiben. Nach einer Weile mag es eine angenehme Überraschung sein, wie selten man veränderbare Variablen braucht.

+1

Dank. Ich merke, dass mein Weg nicht Lispy war, da Nebenwirkungen verpönt sind. Ich habe etwas durchgecheckt (ein Projekt-Euler-Problem) und konnte diesen einfachen Testfall nicht zum Laufen bringen, was beweist, dass ich etwas nicht verstanden habe. Danke für die Hilfe. Ich habe vergessen, dass die Schleife sich wiederholen könnte, das funktioniert sehr sauber (ohne die zusätzliche Funktion Rekursion). – MBCook

+4

Nebenwirkungen sind Lispy abhängig davon, welche Lisp Sie betrachten. In Common Lisp kommst du mit (Schleife für x = 128, dann (/ x 2), während (> x 1) (x drucken)). Aber Nebenwirkungen sind nicht Clojurish. –

+1

Es ist sehr alt Aber das ist eine sehr gute Antwort, ich bin neu in Clojure das rettete mich von Stunden des Kämpfens mit dem gleichen Problem. Vielen Dank @BrianCarper – shan

12

Vars (das ist, was Sie bekommen, wenn Sie "def" etwas) sind 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 verwenden „setzen!“:

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 wr ite eine Schleife wie folgt:

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 unidiomatisch.

5

Wenn Sie der Meinung sind, dass veränderbare lokale Variablen in reinen Funktionen eine nützliche Funktion sind, die keinen Schaden anrichtet, weil die Funktion immer noch rein bleibt, könnten Sie an dieser Mailinglistendiskussion interessiert sein, in der Rich Hickey seine Gründe für das Entfernen erklärt sie von der Sprache. Why not mutable locals?

Relevante Teil:

Wenn Einheimische Variablen waren, also wandelbar, dann Schließungen über wandelbaren Zustand schließen konnte, und, da die Verschlüsse (ohne einige zusätzliche Verbot gleich) entweichen kann, ist das Ergebnis wäre Thread-unsicher. Und Leute würden sicherlich so tun, z.B. Schließ-basierte Pseudo-Objekte. Das Ergebnis wäre ein großes Loch in Clojures Ansatz.

Ohne veränderliche Einheimische, Menschen sind gezwungen, Rezidiv, ein funktionelles Looping-Konstrukt zu verwenden. Obwohl dies auf den ersten Blick seltsam erscheinen mag, ist es genauso einfach wie Schleifen mit Mutation, und die resultierenden Muster können an anderer Stelle in Clojure, d. H. Wiederholen, reduzieren, ändern, pendeln usw. sind alle (logisch) sehr ähnlich. Obwohl ich erkennen konnte und verhinderte, dass mutierende Schließungen entkamen, entschied ich mich, es aus Gründen der Konsistenz so zu halten . Selbst im kleinsten Kontext sind nicht-mutierende Schleifen einfacher zu verstehen und zu debuggen als mutierte. In jedem Fall ist Vars verfügbar, wenn es angemessen ist.

Die Mehrheit der nachfolgenden betrifft Beiträge ein with-local-vars Makro implementiert;)

1

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>