+ Antworten
Ergebnis 1 bis 4 von 4

Thema: Voll vom Lazy-Bug gebissen

  1. #1
    Unregistriert
    Gast/Guest
    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 Code:
    1. (for [x (range 10)]
    2.   (println x))

    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 Code:
    1. (defn f []
    2.   (for [x (range 5)]
    3.     (println x)))
    4.  
    5. (f)

    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 Code:
    1. (defn f []
    2.   (for [x (range 5)]
    3.     (println x))
    4.     "Done!")
    5.  
    6. (f)

    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 Code:
    1. (defn f []
    2.   (count (for [x (range 5)]
    3.     (println x)))
    4.     "Done!")

    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 Code:
    1. (defn f []
    2.   (doseq [x (range 5)]
    3.     (println x))
    4.     "Done!")

    Zu beachten ist hier, dass doseq nil zurückliefern würde, wohingegen das for 5x nil zurückliefern würde.

    Clojure Code:
    1. (defn f []
    2.   (doseq [x (range 5)]
    3.     (println x)
    4.     x))
    5.  
    6. (defn f []
    7.   (for [x (range 5)]
    8.     (do
    9.       (println x)
    10.       x)))

    Bzw. doseq nil und for (0, 1, 2, 3, 4)

  2. #2
    User Viertel Megabyte Avatar von inv_zim
    Registriert seit
    31.07.2013
    Ort
    Rhein-Main Gebiet
    Fachbeiträge
    372
    Genannt
    31 Post(s)
    Hi,

    danke für den kleinen Exkurs, auf jeden Fall interessant das Problem so aufgedröselt zu bekommen.
    Was ich mich nur frage, gibt es überhaupt Vorteile das Konstrukt "for" bei einer funktionalen Sprache zu benutzen?

    Gruß,
    Tim
    I am obsessed with the ancient science of "puzzle-ometry". I have discovered that within puzzles lies the secret of human intelligence, that which separates us from the common beast.

  3. #3
    Unregistered
    Gast/Guest
    Hi Tim,

    Eine Alternative zu "for" ist ein "map". Map ist übrigens auch lazy und sorgt für die gleiche Problematik.
    Das for ist in dem Sinne nicht mit dem for aus Java zu vergleichen.

    Das for-Konstrukt finde ich praktisch wenn über mehrere Listen iteriert wird, z.B. Pixel eines Bildes
    (for [x (range width) y (range height)] (foo x y)) und oft nutze ich das ganze auch um Variablen direkt zu definieren, anstatt hier mit zusätzlichen let's zu hantieren.

    Clojure Code:
    1. (for [x datastructure, y (first x), z(second x)]
    2.   (foo y z))

    Mit map sieht das iterieren über mehrere Listen immer etwas ungemütlich aus. Wobei es hier auch bestimmt coole Varianten gibt.


    Das Problem das ich mit dem lazy hatte war, dass ich eigentlich nichts funktionales, sondern etwas prozedurales gebraucht habe. Etwas mit Seiteneffekten. Da kommt man nicht immer herum. Für den Rest des Projekt habe ich super Clojure DSL's gefunden, aber am Gluecode hat es dann etwas gehakt. Es ist halt ein komisches Gefühl wenn etwas in der Repl Resultate liefert, aber dann in einer main-Methode völlig übersprungen wird.

  4. #4
    Unregistered
    Gast/Guest
    Zitat Zitat von Unregistriert Beitrag anzeigen
    [...]
    dann beißt einen der lazy-Bug. Die Schleife wird übersprungen und es wird lediglich "Done!" zurückgegeben.
    [...]
    Es ist eben so das die for-Form kein "for-loop" ist, ich verstehe aber das man es verwechseln könnte. Das ganze nennt sich "List Comprehension" und die Ergebnisliste ist in Clojure nun leider eine LazySeq. Dabei wird die for-Form nicht übersprungen nur wird die resultierende LazySeq nirgendwo verwendet. Deshalb wie du schon in Erfahrung gebracht hast, entweder doseq, was mehr dem imperativen "for-each" entspricht oder alternativ (wobei man das seltener sieht) man wrappt die for-Form in einer doall-Form.

+ Antworten Thema als "gelöst" markieren

Direkt antworten Direkt antworten

Wie lautet das letzte Wort am Ende dieser Webseite?

Aktive Benutzer

Aktive Benutzer

Aktive Benutzer in diesem Thema: 1 (Registrierte Benutzer: 0, Gäste: 1)

Ähnliche Themen

  1. Showplan Operator of the Week - Lazy Spool
    Von RSS Reader im Forum simple-talk - category SQL
    Antworten: 0
    Letzter Beitrag: 11.06.2010, 21:05
  2. Lazy instantiation of MultiCDockables
    Von shlomy im Forum DockingFrames
    Antworten: 7
    Letzter Beitrag: 22.06.2009, 08:29

Berechtigungen

  • Neue Themen erstellen: Ja
  • Themen beantworten: Ja
  • Anhänge hochladen: Nein
  • Beiträge bearbeiten: Nein
  •