Bienvenue dans le monde de l'évaluation différée.
Quand vous pensez à ce sujet en termes d'évaluation stricte, foldl semble "bon" et foldr semble "mauvais" parce que foldl est la queue récursive, mais foldr aurait pour construire une tour dans la pile de façon à pouvoir traiter le dernier élément de la première.
Cependant, l'évaluation différée tourne les tables. Prenez, par exemple, la définition de la fonction map:
map :: (a -> b) -> [a] -> [b]
map _ [] = []
map f (x:xs) = f x : map f xs
Ce ne serait pas trop bon si Haskell utilisé une évaluation rigoureuse, car elle aurait pour calculer la queue en premier, puis ajouter l'élément (pour tous les éléments de la liste). La seule façon de le faire de manière efficace serait de construire les éléments dans le sens inverse, il me semble.
Cependant, grâce à Haskell l'évaluation différée, cette fonction map est réellement efficace. Les listes en Haskell peuvent être considérés comme des générateurs, et la carte de fonction génère son premier point en application de f vers le premier élément de la liste d'entrée. Quand il a besoin d'une deuxième élément, elle fait exactement la même chose de nouveau (sans utiliser l'espace supplémentaire).
Il s'avère qu' map
peut être décrite en termes de foldr
:
map f xs = foldr (\x ys -> f x : ys) [] xs
Il est difficile de dire en le regardant, mais lazy evaluation des coups de pied dans, car foldr peut donner des f
son premier argument tout de suite:
foldr f z [] = z
foldr f z (x:xs) = f x (foldr f z xs)
Parce que l' f
défini par map
pouvez retourner le premier élément de la liste de résultats en utilisant uniquement le premier paramètre, le pli peut fonctionner paresseusement dans la constante de l'espace.
Maintenant, paresseux évaluation ne morsure de retour. Par exemple, essayez d'exécuter la somme [1..1000000]. Il produit un débordement de pile. Pourquoi devrait-il? Il devrait évaluer de gauche à droite, de droite?
Regardons comment Haskell évalue:
foldl f z [] = z
foldl f z (x:xs) = foldl f (f z x) xs
sum = foldl (+) 0
sum [1..1000000] = foldl (+) 0 [1..1000000]
= foldl (+) ((+) 0 1) [2..1000000]
= foldl (+) ((+) ((+) 0 1) 2) [3..1000000]
= foldl (+) ((+) ((+) ((+) 0 1) 2) 3) [4..1000000]
...
Haskell est trop paresseux pour aller de l'avant et effectuer les ajouts, de sorte que cela donne un débordement de pile. Heureusement, il existe une fonction spéciale dans les Données.Liste appelée foldl'
qui opère strictement. foldl' (+) 0 [1..1000000]
n'aura pas de débordement de pile.
Note: j'ai essayé de remplacer foldl
avec foldl'
dans votre test, mais en fait il fait plus lent.