Oder: Warum funktioniert mein for-loop nicht?
Das ist jetzt weniger eine Frage sondern eher eine Erklärung was mich da die letzte Zeit ziemlich genervt hat.
Angenommen ich habe eine Schleife, die Zahlen bis 10 ausgibt.
[clojure](for [x (range 10)]
(println x))
[/clojure]
Dann funktioniert das soweit erstmal in der Repl. Die Zahlen werden ausgegeben und die Rückgabe ist eine Liste mit 5x nil.
Packt man das ganze in eine Funktion
[clojure](defn f []
(for [x (range 5)]
(println x)))
(f)[/clojure]
Und ruft diese auf, so ist weiterhin alles gut und man erhält das erwartete Verhalten.
Ändert man nun die Funktion, so daß nach der Schleife der String “Done!” zurückgegeben wird
[clojure](defn f []
(for [x (range 5)]
(println x))
“Done!”)
(f)[/clojure]
dann beißt einen der lazy-Bug. Die Schleife wird übersprungen und es wird lediglich “Done!” zurückgegeben.
Ein Punkt an dem man fast wahnsinnig werden kann.
Wie kommt es dazu?
Das for ist keine Schleife im Sinne von Java, sondern ein Macro mit einem Rückgabetyp.
Der Rückgabetyp ist eine lazy-seq. Ähnlich einem Iterator in Java.
Und ein Iterator mach sich nur die Arbeit wenn auch tatsächlich darüber iteriert wird.
Da in Clojure das letzte Element einer Funktion ein Rückgabewert ist, wird es in den ersten Fällen zurückgegeben und in der Repl evaluiert und somit ausgegeben.
Im letzten Fall, wird der Iterator nur in der Methode definiert aber nie aufgerufen, was dazu führt das der Schleifeninhalt wie übersprungen wirkt. Zurückgegeben wird nur der String “Done!”.
Was tun?
Eine Möglichkeit ist eine Funktion auf die lazy-seq anzuwenden. z.B. count um die Elemente zu zählen. Das funktioniert zwar ist aber eher ein Schuß von Hinten durch die Brust ins Auge.
[clojure](defn f []
(count (for [x (range 5)]
(println x)))
“Done!”)[/clojure]
Oder man verwendet das wohl korrekte doseq. Das hat die gleiche Syntax wie for, ist aber nicht so lazy.
Die Doku erwähnt sogar (presumably for side-effects) was die Ausgabe in der Konsole in der Tat ist.
[clojure](defn f []
(doseq [x (range 5)]
(println x))
“Done!”)[/clojure]
Zu beachten ist hier, dass doseq nil zurückliefern würde, wohingegen das for 5x nil zurückliefern würde.
[clojure](defn f []
(doseq [x (range 5)]
(println x)
x))
(defn f []
(for [x (range 5)]
(do
(println x)
x)))[/clojure]
Bzw. doseq nil und for (0, 1, 2, 3, 4)