Haskell foldl, foldr, foldl'

 

Haskell foldl, foldr, foldl'

 

Haskell foldl, foldr, foldl'

总是没能分清它们的区别,试着总结一下。

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' 该用谁?

我们可以试着再小结一下:

  1. 尽量避免使用 foldl,除非 list 是有限长。从上面的分析知道,foldl 的递归展开会消 耗巨大的 stack,完全没有利用上 haskell 的 lazy evaluation.
  2. 大多数情况下(通常返回结果也是 list)可以使用 foldr。foldr 虽然从右向左处理 list,但因为 f 的左值总是立刻可以得到的,所以通常能处理无限长 list。
  3. 对于需要算出立即数,不需要 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)

Date: 2012-07-04 22:23:25 CST

Author: William Xu

Org version 7.8.11 with Emacs version 24

Validate XHTML 1.0
posted on 2012-07-04 22:28  william9  阅读(493)  评论(0编辑  收藏  举报