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!foldl
als "rückwärts" undfoldr
als "vorwärts" problematisch. Dies liegt zum Teil daran,flip
dass(:)
in der Abbildung angewendet wird, warum die Falte rückwärts ist. Die natürliche Reaktion ist: "Natürlich ist es rückwärts: Sieflip
ped Liste Verkettung!" Es ist auch seltsam zu sehen, dass "rückwärts" genannt wird, da diesfoldl
fürf
das erste Listenelement zuerst (innerste) in einer vollständigen Bewertung gilt. Es ist so,foldr
dass "rückwärts läuft" und zuerstf
auf das letzte Element angewendet wird.foldl
undfoldr
und Ignorieren von Strenge und Optimierungen bedeutet zunächst "äußerste", nicht "innerste". Dies ist der Grund, warumfoldr
unendliche Listen verarbeitet werden können undfoldl
nicht - die rechte Falte gilt zuerst fürf
das erste Listenelement und das (nicht bewertete) Ergebnis des Faltens des Schwanzes, während die linke Falte die gesamte Liste durchlaufen muss, um die äußerste Anwendung von zu bewertenf
.last xs = foldl (\a z-> z) undefined xs
.etc.
Intuitiv
foldl
ist immer auf der "Außenseite" oder auf der "Linken", so dass es zuerst erweitert wird. Ad infinitum.quelle
Sie können in Haskells Dokumentation hier sehen, dass foldl rekursiv ist und niemals endet, wenn eine unendliche Liste übergeben wird, da es sich beim nächsten Parameter aufruft, bevor ein Wert zurückgegeben wird ...
quelle
Ich kenne Haskell nicht, aber in Schema
fold-right
wird immer zuerst auf das letzte Element einer Liste "reagiert". Daher funktioniert es nicht für zyklische Listen (die mit einer unendlichen identisch sind).Ich bin nicht sicher, ob
fold-right
schwanzrekursiv geschrieben werden kann, aber für jede zyklische Liste sollten Sie einen Stapelüberlauf erhalten.fold-left
OTOH wird normalerweise mit Schwanzrekursion implementiert und bleibt nur in einer Endlosschleife stecken, wenn sie nicht vorzeitig beendet wird.quelle