Wie foldunterschiedlich die Unterschiede sind, scheint eine häufige Quelle der Verwirrung zu sein. Hier ein allgemeinerer Überblick:
Ziehen Sie in Betracht, eine Liste von n Werten [x1, x2, x3, x4 ... xn ]mit einer Funktion fund einem Startwert zu falten z.
foldl ist:
- Linker Assoziativ :
f ( ... (f (f (f (f z x1) x2) x3) x4) ...) xn
- Schwanz rekursiv : Es durchläuft die Liste und erzeugt anschließend den Wert
- Faul : Nichts wird ausgewertet, bis das Ergebnis benötigt wird
- Rückwärts :
foldl (flip (:)) []Kehrt eine Liste um.
foldr ist:
- Richtiger Assoziativ :
f x1 (f x2 (f x3 (f x4 ... (f xn z) ... )))
- Rekursiv in ein Argument : Jede Iteration gilt
ffür den nächsten Wert und das Ergebnis des Faltens des Restes der Liste.
- Faul : Nichts wird ausgewertet, bis das Ergebnis benötigt wird
- Forwards :
foldr (:) []gibt eine Liste unverändert.
Es ist ein etwas subtiler Punkt hier , dass Reisen Personen manchmal: Da foldlist rückwärts jede Anwendung fauf den hinzugefügt wird außerhalb des Ergebnisses; und weil es faul ist , wird nichts ausgewertet, bis das Ergebnis benötigt wird. Dies bedeutet , dass irgendein Teil des Ergebnisses, Haskell ersten iteriert durch die berechnen gesamte Liste Konstruktion eines Expressions von verschachtelten Funktionsanwendungen, wertet dann die äußerste Funktion, Bewertung ihrer Argumente wie erforderlich. Wenn fimmer das erste Argument verwendet wird, bedeutet dies, dass Haskell bis zum innersten Begriff zurückgreifen und dann jede Anwendung von rückwärts berechnen muss f.
Dies ist offensichtlich weit entfernt von der effizienten Schwanzrekursion, die die meisten funktionalen Programmierer kennen und lieben!
In der Tat kann es zu einem Stapelüberlauf kommen , obwohl dies foldltechnisch rekursiv ist, da der gesamte Ergebnisausdruck vor der Auswertung erstellt wird foldl!
Auf der anderen Seite überlegen foldr. Es ist auch faul, aber da es vorwärts läuft , wird jede Anwendung von fdem Inneren des Ergebnisses hinzugefügt . Um das Ergebnis zu berechnen, erstellt Haskell eine einzelne Funktionsanwendung, deren zweites Argument der Rest der gefalteten Liste ist. Wenn fdas zweite Argument - beispielsweise ein Datenkonstruktor - faul ist , ist das Ergebnis inkrementell faul , wobei jeder Schritt der Falte nur berechnet wird, wenn ein Teil des Ergebnisses ausgewertet wird, der es benötigt.
So können wir sehen, warum foldrmanchmal unendliche Listen foldlfunktionieren, wenn dies nicht der Fall ist: Ersteres kann eine unendliche Liste träge in eine andere träge unendliche Datenstruktur konvertieren, während letzteres die gesamte Liste untersuchen muss, um einen Teil des Ergebnisses zu generieren. Auf der anderen Seite foldrfunktioniert eine Funktion, die beide Argumente sofort benötigt, z. B. (+)funktioniert (oder funktioniert eher nicht), ähnlich wie das foldlErstellen eines großen Ausdrucks vor dem Auswerten.
Die beiden wichtigsten Punkte sind also:
foldr kann eine träge rekursive Datenstruktur in eine andere umwandeln.
- Andernfalls stürzen faule Falten mit einem Stapelüberlauf auf großen oder unendlichen Listen ab.
Sie haben vielleicht bemerkt, dass es so klingt, als ob foldrSie alles foldlkönnen, und noch mehr. Das ist wahr! In der Tat ist Foldl fast nutzlos!
Aber was ist, wenn wir ein nicht faules Ergebnis erzielen wollen, indem wir eine große (aber nicht unendliche) Liste falten? Dafür wollen wir eine strikte Falte , die die Standardbibliotheken sorgfältig bereitstellen :
foldl' ist:
- Linker Assoziativ :
f ( ... (f (f (f (f z x1) x2) x3) x4) ...) xn
- Schwanz rekursiv : Es durchläuft die Liste und erzeugt anschließend den Wert
- Streng : Jede Funktionsanwendung wird auf dem Weg bewertet
- Rückwärts :
foldl' (flip (:)) []Kehrt eine Liste um.
Weil foldl'es streng ist, das Ergebnis zu berechnen, wird Haskell bei jedem Schritt auswerten f , anstatt das linke Argument einen riesigen, nicht bewerteten Ausdruck ansammeln zu lassen. Dies gibt uns die übliche, effiziente Schwanzrekursion, die wir wollen! Mit anderen Worten:
foldl' kann große Listen effizient falten.
foldl' hängt in einer Endlosschleife (verursacht keinen Stapelüberlauf) in einer Endlosliste.
Das Haskell-Wiki hat auch eine Seite, auf der dies diskutiert wird.
foldres besser ist alsfoldlin Haskell , während das Gegenteil in Erlang (das ich vor Haskell gelernt habe ) zutrifft . Da Erlang ist nicht faul und Funktionen sind nicht curried , sofoldlin Erlang verhält sich wiefoldl'oben. Das ist eine großartige Antwort! Gute Arbeit und danke!