it-swarm.com.de

Implikationen von foldr vs. foldl (oder foldl ')

Erstens sagt Real World Haskell, das ich gerade lese, niemals foldl und stattdessen foldl' Zu verwenden. Also vertraue ich darauf.

Aber ich weiß nicht, wann ich foldr gegen foldl' Verwenden soll. Obwohl ich die Struktur sehen kann, wie sie auf unterschiedliche Weise vor mir liegen, bin ich zu dumm, um zu verstehen, wann "was besser ist". Ich denke, es scheint mir, dass es eigentlich egal sein sollte, was verwendet wird, da beide die gleiche Antwort liefern (oder?). Meine bisherigen Erfahrungen mit diesem Konstrukt stammen von Rubys inject und Clojures reduce, die anscheinend keine "left" - und "right" -Versionen haben. (Nebenfrage: Welche Version verwenden sie?)

Jede Einsicht, die einer klugen Sorte wie mir helfen kann, wäre sehr dankbar!

146
J Cooper

Die Rekursion für foldr f x ys wo ys = [y1,y2,...,yk] sieht aus wie

f y1 (f y2 (... (f yk x) ...))

in der Erwägung, dass die Rekursion für foldl f x ys sieht aus wie

f (... (f (f x y1) y2) ...) yk

Ein wichtiger Unterschied ist, dass, wenn das Ergebnis von f x y kann nur mit dem Wert von x berechnet werden, dann muss foldr nicht die gesamte Liste untersuchen. Beispielsweise

foldr (&&) False (repeat False)

gibt False zurück, wohingegen

foldl (&&) False (repeat False)

endet nie. (Hinweis: repeat False erstellt eine unendliche Liste, in der jedes Element False ist.)

Auf der anderen Seite, foldl' ist rekursiv und streng. Wenn Sie wissen, dass Sie die gesamte Liste durchlaufen müssen, egal was passiert (z. B. Summieren der Zahlen in einer Liste), dann foldl' ist platz- (und wahrscheinlich zeit-) effizienter als foldr.

165
Chris Conway

foldr sieht so aus:

Right-fold visualization

foldl sieht so aus:

Left-fold visualization

Kontext: Fold im Haskell-Wiki

52
Greg Bacon

Ihre Semantik unterscheidet sich, sodass Sie foldl und foldr nicht einfach vertauschen können. Der eine faltet die Elemente von links hoch, der andere von rechts. Auf diese Weise wird der Operator in einer anderen Reihenfolge angewendet. Dies gilt für alle nicht assoziativen Operationen, z. B. für die Subtraktion.

Haskell.org hat einen interessanten Artikel zum Thema.

33
Konrad Rudolph

Kurz gesagt ist foldr besser, wenn die Akkumulatorfunktion bei ihrem zweiten Argument faul ist. Lesen Sie mehr bei Haskell Wikis Stapelüberlauf (Wortspiel beabsichtigt).

23
mattiast

Der Grund, warum foldl' Für 99% aller Verwendungen foldl vorgezogen wird, ist, dass es für die meisten Verwendungen im konstanten Speicher ausgeführt werden kann.

Nimm die Funktion sum = foldl['] (+) 0. Wenn foldl' Verwendet wird, wird die Summe sofort berechnet, sodass die Anwendung von sum auf eine unendliche Liste nur für immer und höchstwahrscheinlich im konstanten Raum ausgeführt wird (wenn Sie Dinge wie Int s, Double s, Float s. Integer s belegen mehr als einen konstanten Abstand, wenn die Zahl größer als maxBound :: Int Wird.

Mit foldl wird ein Thunk aufgebaut (wie ein Rezept zum Abrufen der Antwort, das später ausgewertet werden kann, anstatt die Antwort zu speichern). Diese Thunks können viel Platz in Anspruch nehmen. In diesem Fall ist es viel besser, den Ausdruck zu bewerten, als den Thunk zu speichern (was zu einem Stapelüberlauf führt… und Sie dazu führt, dass… oh, egal).

Ich hoffe, das hilft.

18
Axman6

Übrigens sind Rubys inject und Clojures reducefoldl (oder foldl1, abhängig von der verwendeten Version). Wenn es in einer Sprache nur eine Form gibt, handelt es sich normalerweise um eine linke Falte, einschließlich Pythons reduce, Perls List::Util::reduce, C++ 's accumulate, C #' s Aggregate, Smalltalk's inject:into:, PHP's array_reduce, Mathematica's Fold, etc. Common LISP's reduce falten standardmäßig nach links, aber es gibt eine Option für die rechte Falte.

14
newacct

Wie Konrad betont, ist ihre Semantik unterschiedlich. Sie haben nicht einmal den gleichen Typ:

ghci> :t foldr
foldr :: (a -> b -> b) -> b -> [a] -> b
ghci> :t foldl
foldl :: (a -> b -> a) -> a -> [b] -> a
ghci> 

Beispielsweise kann der Operator zum Anhängen von Listen (++) mit foldr as implementiert werden

(++) = flip (foldr (:))

während

(++) = flip (foldl (:))

wird Ihnen einen Tippfehler geben.

6
Jonas