我正在努力理解foldr和foldl,使用其中的主要好处是什么,foldl应该是尾递归,但那么什么是foldr,通常是递归的?
我有一个简单的函数,它接受int的列表并返回第一个和最后一个元素之间的平方和。
sumSquare :: [Int] -> Int
sumSquare xs = if length xs == 2 then sum $ map (^2) [head xs..last xs] else error "bad input"
我想要做的是使用foldr或foldl实现这个功能,我已经看了几个例子,但遗憾的是无处可去。
如果有人可以演示如何使用foldr或foldl将此函数转换为一个函数并解释过程我将不胜感激
答案 0 :(得分:3)
一些不完美的经验法则:
foldl
。foldr
。foldl'
模块中的Data.List
。第4点的例子:哪个更容易理解?例1:
example1 = foldr step [] [0..]
where step n ns = if even n then n:ns else ns
示例2:
filter :: (a -> Bool) -> [a] -> [a]
filter p = foldr step []
where step a as = if p a then a:as else as
example2 = filter even [0..]
第二个,当然!您希望通过将问题分解为更小的问题来解决问题,并将问题分解为更小的问题。当折叠发生在分裂的底部时,这个过程最容易理解。
第2点和第3点:考虑sum :: Num a => [a] -> a
和filter :: (a -> Bool) -> [a] -> [a]
之间的区别。 sum
的结果是一个数字,filter
的结果是一个列表。在Haskell中,可以懒惰地生成和使用列表,但标准数字类型不能。
如果你正在构建的结果是一个列表,你想使用foldr
,因为实际发生的是结果将被懒惰地构造,因为需要列表。例如,上面的filter
定义就是这样; filter
生成的列表将根据消费者的需求而懒散地构建。示例(如上定义中step
):
head (filter even [0..])
= head (foldr step [] [0..])
= head (step 0 (foldr step [] [1..]))
= head (if even 0 then 0 : foldr step [] [1..] else foldr step [] [1..])
= head (if True then 0 : foldr step [] [1..] else foldr step [] [1..])
= head (0 : foldr step [] [1..])
= 0
这里的懒惰意味着foldr
的评估会在产生足够的结果时立即停止以找出head
的结果。
但如果你正在sum
,你很可能想要使用foldl'
:
import Data.List (foldl')
sum :: Num a => [a] -> a
sum = foldl' (+) 0
由于懒惰无法使用数字,因此您希望利用foldl'
的尾递归。例如:
sum [1..3] * 2
= foldl' (+) 0 [1..3] * 2
= foldl' (+) 1 [2..3] * 2
= foldl' (+) 3 [3..3] * 2
= foldl' (+) 6 [] * 2
= 6 * 2
= 12
但另一方面,foldl'
无法像foldr
那样利用懒惰。 foldl'
必须一次遍历整个列表。
回答评论:好吧,让我们再试一次。我要说的第一点是,你要做的是坏主意。你不想一次性做“3-4件事”。它产生的代码难以阅读且难以修改。
但是有一种方法可以把它变成一个很好的例子。让我们用这样的方式编写你的函数:
sumSquares (lo, hi) = sum $ map (^2) [lo..hi]
请注意,我将参数更改为一对。你的原文要求列表的长度恰好是两个,这实际上说你不应该使用列表,而是一对(或者只是两个参数)。
那么如何将其变成一组折叠?第一步:让我们用折叠来写sum
和map
:
sum = foldr (+) 0
map f = foldr (\a bs -> f a : bs) []
现在我们可以手动将这些内容内联到定义中:
sumSquares (lo, hi) = foldr (+) 0 $ foldr (\a bs -> a^2 : bs) [] [lo..hi]
现在,这是一个非常有用的事实:如果foldr
使用由另一个foldr
生成的列表,则通常可以将融合两个折叠成一个。在这种情况下,他们融合了这个:
sumSquares (lo, hi) = foldr (\a bs -> a^2 + bs) 0 [lo..hi]
例如:
sumSquares (1, 3)
= foldr (\a bs -> a^2 + bs) 0 [1..3]
= 1^2 + foldr (\a bs -> a^2 + bs) 0 [2..3]
= 1^2 + 2^2 + foldr (\a bs -> a^2 + bs) 0 [3..3]
= 1^2 + 2^2 + + 3^2 + foldr (\a bs -> a^2 + bs) 0 []
= 1^2 + 2^2 + + 3^2 + 0
= 1 + 4 + 9 + 0
= 14
作为一个侧面点,您在代码中使用head
和last
,请注意这些也可以写为折叠:
safeHead, safeLast :: [a] -> Maybe a
safeHead = foldr step Nothing
where step a _ = Just a
safeLast = foldr step Nothing
where step a Nothing = Just a
step _ justA = justA
因此,要以凌乱的一体化功能方式解决这些问题,您需要写出干净的多功能解决方案,将各个部分翻译成折叠,然后弄清楚是否以及如何融合那些折叠在一起。
但作为初学者的练习,这并不值得。首先,GHC has list fusion optimizations built in already。所以编译器已经知道如何融合这段代码并将其转换为一个漂亮,紧凑的循环:
foldr (+) 0 $ map (^2) [lo..hi]
答案 1 :(得分:1)
@Luis Casillas给出了很好的建议,但对于你的sumSquares:
如果查看类型foldr :: (a -> b -> b) -> b -> [a] -> b
,您会看到它需要一个函数(接受一个元素,累加器,并返回一个新的累加器),初始累加器和一个列表并返回结果累加器。对于foldl :: (b -> a -> b) -> b -> [a] -> b
,参数的顺序是相反的。
所以sumSquares可以写(我冒昧地稍微更改它以删除长度检查)
sumSquare' a b = foldl (\x y -> x + (y^2)) 0 [a..b]
sumSquare'' a b = foldr (\y x -> x + (y^2)) 0 [a..b]
关于它们如何递归,让我们看看它们是如何定义的: (有关此问题的更多信息,http://en.wikibooks.org/wiki/Haskell/List_processing和https://wiki.haskell.org/Foldr_Foldl_Foldl'有很好的解释。
foldl :: (a -> b -> a) -> a -> [b] -> a
foldl f acc [] = acc
foldl f acc (x:xs) = foldl f (f acc x) xs
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr f acc [] = acc
foldr f acc (x:xs) = f x (foldr f acc xs)
因此,sumSquare' 1 3
的评估将是(f
表示lambda)
foldl f 0 [1,2,3]
foldl f (f 0 1) [2,3]
foldl f (f (f 0 1) 2) [3]
foldl f (f (f (f 0 1) 2 ) 3) []
f (f (f 0 1) 2 ) 3
和sumSquare'' 1 3
:
foldr f 0 [1,2,3]
f 1 (foldr f 0 [2,3])
f 1 (f 2 (foldr f 0 [3]))
f 1 (f 2 (f 3 (foldr f 0 []))
f 1 (f 2 (f 3 0))