Haskell foldl, foldr, foldl'
Haskell foldl, foldr, foldl'
Haskell foldl, foldr, foldl'
总是没能分清它们的区别,试着总结一下。
Table of Contents
1 究竟谁是从左到右?
单从表面来看,
foldl | 从左至右处理 list |
foldr | 从右至左处理 list |
foldl' | foldl 的非递归版本 |
看几个例子:
foldl (-) 1 [2, 3] => (1 - 2) - 3 = -4 foldr (-) 1 [2, 3] => 2 - (3 - 1) = 0
但是为什么会有 foldl' 而没有 foldr'? 再看这个例子:
foldl (||) True (repeat True) => never returns! foldr (||) True (repeat True) => True
按理说 foldl 从左向右处理,foldr 从右向左处理,出问题的应该是 foldr 才对,却为什 么是 foldl 呢?让我们试着单步展开一下。首先看 foldl:
foldl f zero [a, b, c] -> foldl f (f zero a) [b, c] -> foldl f (f (f zero a) b) [c] -> foldl f (f (f (f zero a) b) c) [] -> (f (f (f zero a) b) c)
注意到最后的表达式:(f (f (f zero a) b) c) 原来在这样的递归操作下,为了求值,必需 一直求出最内层函数调用才能算出其左边参数的值!因此,如果让 foldl 处理一个无限长的 list,必死无疑!
类似的,我们再看一下 foldr 是如何递归的:
foldr f zero [a, b, c] -> foldr f (f c zero) [a, b] -> foldr f (f b (f c zero)) [a] -> foldr f (f a (f b (f c zero))) [] -> (f a (f b (f c zero)))
同样观察一下它的最后一个递归式:(f a (f b (f c zero))) 跟 foldl 对比一下:
foldl | (f (f (f zero a) b) c) |
foldr | (f a (f b (f c zero))) |
很明显,对于 foldr 来说,函数 f 的左值总是立刻可以得到的,这样加上 haskell 的 lazy evaluation,foldr 就可以处理一些无限长的 list. 但是,对于那些需要立即得到其 值,例如 reduce 之类的操作,那么 foldl, foldr 很可能都会挂掉。比如:
-- 设置较小的 stack,方便测试: $ ghci +RTS -K35K -RTS Prelude> foldl (+) 0 [1..5000] *** Exception: stack overflow Prelude> foldr (+) 0 [1..5000] *** Exception: stack overflow Prelude> import Data.List Prelude Data.List> foldl' (+) 0 [1..5000] 12502500 Prelude Data.List>
2 foldl, foldr, fold' 该用谁?
我们可以试着再小结一下:
- 尽量避免使用 foldl,除非 list 是有限长。从上面的分析知道,foldl 的递归展开会消 耗巨大的 stack,完全没有利用上 haskell 的 lazy evaluation.
- 大多数情况下(通常返回结果也是 list)可以使用 foldr。foldr 虽然从右向左处理 list,但因为 f 的左值总是立刻可以得到的,所以通常能处理无限长 list。
- 对于需要算出立即数,不需要 lazy evaluation 之类的运算,尤其是数值运算,通常应 该用 foldl'。
3 看看它们的真正实现
foldl :: (a -> b -> a) -> a -> [b] -> a foldl f z0 xs0 = lgo z0 xs0 where lgo z [] = z lgo z (x:xs) = lgo (f z x) xs foldl' :: (a -> b -> a) -> a -> [b] -> a foldl' f a [] = a foldl' f a (x:xs) = let a' = f a x in a' `seq` foldl' f a' xs foldr :: (a -> b -> b) -> b -> [a] -> b foldr _ z [] = z foldr f z (x:xs) = f x (foldr f z xs)