Wie fold
unterschiedlich 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 f
und 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
f
fü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 foldl
ist rückwärts jede Anwendung f
auf 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 f
immer 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 foldl
technisch 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 f
dem 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 f
das 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 foldr
manchmal unendliche Listen foldl
funktionieren, 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 foldr
funktioniert eine Funktion, die beide Argumente sofort benötigt, z. B. (+)
funktioniert (oder funktioniert eher nicht), ähnlich wie das foldl
Erstellen 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 foldr
Sie alles foldl
kö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.
foldr
es besser ist alsfoldl
in Haskell , während das Gegenteil in Erlang (das ich vor Haskell gelernt habe ) zutrifft . Da Erlang ist nicht faul und Funktionen sind nicht curried , sofoldl
in Erlang verhält sich wiefoldl'
oben. Das ist eine großartige Antwort! Gute Arbeit und danke!