Witamy w świecie leniwego oceniania.
Kiedy myślisz o tym w kategoriach ścisłej oceny, foldl wygląda "dobrze", a foldr wygląda "źle", ponieważ foldl jest rekurencyjny, ale foldr musiałby zbudować wieżę w stosie, aby mógł przetworzyć ostatni element pierwszy.
Jednak leniwe oceny zamieniają tabele. Weźmy, na przykład, definicja funkcji mapy:
map :: (a -> b) -> [a] -> [b]
map _ [] = []
map f (x:xs) = f x : map f xs
to nie byłoby zbyt dobre, jeśli Haskell wykorzystywane szczegółową ocenę, ponieważ musiałaby obliczyć ogon, następnie dołączana element (dla wszystkich pozycji na liście). Jedynym sposobem, aby zrobić to sprawnie, wydaje się być zbudowanie elementów w odwrotnym kierunku.
Jednak dzięki leniwej ocenie Haskella ta funkcja mapy jest rzeczywiście wydajna. Listy w Haskell można uważać za generatory, a ta funkcja mapy generuje pierwszy element, stosując f do pierwszego elementu listy wejściowej. Kiedy potrzebuje drugiego przedmiotu, po prostu robi to samo (bez dodatkowej przestrzeni).
Okazuje się, że map
można opisać w kategoriach foldr
:
map f xs = foldr (\x ys -> f x : ys) [] xs
Trudno powiedzieć, patrząc na niego, ale ocena leniwy rzutach ponieważ foldr może dać f
swój pierwszy argument od zaraz:
foldr f z [] = z
foldr f z (x:xs) = f x (foldr f z xs)
Ponieważ f
określa map
może powrócić do pierwszej pozycji na liście wpływającej wyłącznie pierwszy parametr zagięcia mogą działać leniwie CO Nstant przestrzeń.
Teraz leniwe oceny nie gryzą.Na przykład spróbuj uruchomić sumę [1..1000000]. Daje to przepełnienie stosu. Dlaczego miałby to robić? Powinien po prostu ocenić od lewej do prawej, prawda? wygląd
Miejmy na jak Haskell ocenia go:
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]
...
= (+) ((+) ((+) (...) 999999) 1000000)
Haskell jest zbyt leniwy, aby wykonać uzupełnień jak to idzie. Zamiast tego kończy się wieżą niedoszacowanych ciosów, które muszą zostać zmuszone do zdobycia liczby. Przepełnienie stosu występuje podczas tej oceny, ponieważ musi się głęboko rekurencyjnie ocenić wszystkie uderzenia.
Na szczęście istnieje specjalna funkcja w Data.List o nazwie foldl'
, która działa ściśle. foldl' (+) 0 [1..1000000]
nie będzie stosować przepełnienia. (Uwaga: Próbowałem zastępując foldl
z foldl'
w teście, ale faktycznie działać wolniej.)
Nawiasem mówiąc, użyłbym konstruktora list do napisania 'a' jako' a = (:) ' –
yes. Jedynym powodem, dla którego zrobiłem to ([x] ++) było sprawienie, by aib były jak najbliżej, tak aby porównać fałdowanie tak ściśle jak mogłem – Ori
... i 'b' jako' b = flip (:) '. –