web-dev-qa-db-fra.com

Implications de foldr vs foldl (ou foldl ')

Premièrement, Real World Haskell, que je lis, dit de ne jamais utiliser foldl et d'utiliser à la place foldl'. Je lui fais donc confiance.

Mais je ne sais pas quand utiliser foldr vs foldl'. Bien que je puisse voir la structure de leur fonctionnement différemment présentée devant moi, je suis trop stupide pour comprendre quand "ce qui est mieux". Je suppose qu'il me semble que cela ne devrait pas vraiment avoir d'importance, car ils produisent tous les deux la même réponse (n'est-ce pas?). En fait, mon expérience précédente avec cette construction provient de Ruby inject et de Clojure reduce, qui ne semblent pas avoir de versions "gauche" et "droite". (Question secondaire: quelle version utilisent-ils?)

Tout aperçu qui peut aider un type intelligent comme moi serait très apprécié!

146
J Cooper

La récursivité pour foldr f x ysys = [y1,y2,...,yk] ressemble à

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

alors que la récursivité pour foldl f x ys ressemble à

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

Une différence importante ici est que si le résultat de f x y peut être calculé en utilisant uniquement la valeur de x, alors foldr n'a pas besoin d'examiner la liste entière. Par exemple

foldr (&&) False (repeat False)

renvoie False alors que

foldl (&&) False (repeat False)

ne se termine jamais. (Remarque: repeat False crée une liste infinie où chaque élément est False.)

D'autre part, foldl' est récursif et strict. Si vous savez que vous devrez parcourir toute la liste quoi qu'il arrive (par exemple, en additionnant les nombres dans une liste), alors foldl' est plus efficace en termes d'espace (et probablement de temps) que foldr.

165
Chris Conway

foldr ressemble à ceci:

Right-fold visualization

foldl ressemble à ceci:

Left-fold visualization

Contexte: Fold sur le wiki Haskell

52
Greg Bacon

Leur sémantique étant différente, vous ne pouvez pas simplement échanger foldl et foldr. L'un plie les éléments vers le haut à gauche, l'autre à droite. De cette façon, l'opérateur est appliqué dans un ordre différent. Cela est important pour toutes les opérations non associatives, telles que la soustraction.

Haskell.org a une intéressante article sur le sujet.

33
Konrad Rudolph

En bref, foldr est meilleur lorsque la fonction accumulateur est paresseuse sur son deuxième argument. En savoir plus sur le wiki de Haskell Stack Overflow (jeu de mots voulu).

23
mattiast

La raison pour laquelle foldl' Est préféré à foldl pour 99% de toutes les utilisations est qu'il peut s'exécuter dans un espace constant pour la plupart des utilisations.

Prenez la fonction sum = foldl['] (+) 0. Lorsque foldl' Est utilisé, la somme est immédiatement calculée, donc l'application de sum à une liste infinie s'exécutera pour toujours, et très probablement dans un espace constant (si vous utilisez des choses comme Int s, Double s, Float s. Integer s utilisera plus qu'un espace constant si le nombre devient supérieur à maxBound :: Int).

Avec foldl, un thunk est construit (comme une recette sur la façon d'obtenir la réponse, qui peut être évaluée plus tard, plutôt que de stocker la réponse). Ces thunks peuvent prendre beaucoup d'espace, et dans ce cas, il vaut bien mieux évaluer l'expression que de stocker le thunk (conduisant à un débordement de pile ... et vous menant à ... oh tant pis)

J'espère que ça t'as aidé.

18
Axman6

Soit dit en passant, inject de Ruby et reduce de Clojure sont foldl (ou foldl1, Selon la version que vous utilisez). Habituellement, lorsqu'il n'y a qu'une seule forme dans un langage, c'est un pli gauche, y compris reduce de Python, List::Util::reduce De Perl, accumulate de C++, Aggregate de C # , inject:into: De Smalltalk, array_reduce De PHP, Fold de Mathematica, etc. reduce par défaut de LISP par défaut est le pli gauche mais il y a une option pour le pli droit.

14
newacct

Comme le souligne Konrad , leur sémantique est différente. Ils n'ont même pas le même type:

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

Par exemple, l'opérateur d'ajout de liste (++) peut être implémenté avec foldr comme

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

tandis que

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

vous donnera une erreur de type.

6
Jonas