我们有foldl
can be implemented in terms of foldr
。这在A tutorial on the universality and expressiveness of fold中有详细解释。在论文中指出:
相反,由于
fold
在其列表参数的尾部严格但foldl
,因此无法根据foldl
重新定义fold
foldr
不是。
但是我没有看到这是如何构成关于无法用foldl
来定义fold
的证明(注意在原始论文中foldr
是{{1}的同义词}})。我很困惑,试图理解严格性在这里发挥作用。有人可以扩展这个解释吗?
答案 0 :(得分:5)
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr f acc [] = acc
foldr f acc (x : xs) = f x (foldr f acc xs)
请注意,当列表不为空时,对foldr
的递归调用 in 传递给f
的参数。 Haskell被懒惰地评估,因此对foldr
的递归调用不一定是实际计算的。如果f
可以在不检查第二个参数的情况下返回结果(但是可以检查第一个参数x
到决定它是否想要看看它的第二个参数),然后foldr
调用可以“提前停止”,而不检查整个列表。
foldl :: (b -> a -> b) -> b -> [a] -> b
foldl f acc [] = acc
foldl f acc (x : xs) = foldl (f acc x) xs
这里(当列表不为空时)foldl
立即递归,在累加器参数内调用f
,它传递给递归调用。 f
没有机会决定是否检查递归调用; foldl
可以完全控制继续递归的时间,并且一直这样做,直到找到列表的末尾。
特别是,使用foldr
,您可以处理无限列表;只要你传递它的函数最终不引用它的第二个参数(acc
)。例如在GHCi中:
Prelude> foldr (\x acc -> if x > 10 then "..." else show x ++ ", " ++ acc) "STOP" [1..5]
"1, 2, 3, 4, 5, STOP"
Prelude> foldr (\x acc -> if x > 10 then "..." else show x ++ ", " ++ acc) "STOP" [1..]
"1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ..."
使用foldr
,lambda函数能够使用if
语句检查列表元素x
并决定是否使用折叠列表其余部分的结果(由acc
引用但尚未实际计算过。)
或者,该函数可以返回包括递归调用的结果,但将其存储在数据构造函数的字段中。这意味着foldr
将生成无限嵌套的数据结构,但允许foldr
的调用者决定它想要查看的无限结构中有多少,因此调用者可能仍然能够返回处理它的有限结果。一个例子是:
Prelude> take 10 $ foldr (\x acc -> x + 100 : acc) [] [1..]
[101,102,103,104,105,106,107,108,109,110]
这里我只是使用foldr
来完成与map
相同的工作,向无限列表的每个元素添加100,这依次产生一个无限列表,但是{{1}只抓住前10个。这是有效的,因为无限的“折叠列表结果的结果”只是存储在列表构造函数take 10
的第二个字段中。
:
无法模拟处理无限列表的能力(无论您是否实际返回有限或无限结果),因为您传递给foldl
的函数无法确定递归的“多少”; foldl
本身一直递归到列表的末尾,然后返回除foldl
的另一个调用之外的任何内容。如果列表是无限的,它根本不会返回除foldl
的另一个调用之外的任何内容,无论您传递给foldl
的函数是什么,或者您使用什么包装来尝试使它完成{{1}的工作}}
这不仅仅是处理无限列表(如果这似乎是深奥的);如果你的所有列表都是有限的,那么可以使用foldl
来获得与foldr
相同的结果,但是你仍然无法检查整个列表,这可能效率很低如果你实际上只需要一个小的前缀。
答案 1 :(得分:4)
在下文中,我将fold
称为foldr
(因为这是在Haskell标准库中调用的内容)。
再看一下foldl
的定义,
foldl :: (β -> α -> β) -> β -> [α] -> β
foldl f v [] = v
foldl f v (x:xs) = foldl f (f v x) xs
请注意,基本情况需要list参数为[]
- 在所有其他情况下,列表将被评估为WNHF,我们立即使用列表的尾部递归。这确定了foldl
在其(最后)列表参数中是严格的事实。
现在,我们可以使用foldr
编写foldl
版本,但它只适用于有限长度的列表。请参阅this page了解动机。
foldr' :: (α -> β -> β) -> β -> [α] -> β
foldr' f v xs = foldl (\g u x -> g (f u x)) id xs v
要查看问题,请尝试使用右侧折叠编写head
版本,即总 1 。
import Control.Applicative ((<|>))
safeHead :: [α] -> Maybe α
safeHead = foldr (\x y -> Just x <|> y) Nothing
如果我们使用基于foldr
的{{1}}替换foldl
,我们将无法再找到无限列表foldr'
的头部。即使safeHead [1..]
只需要查看列表的第一个元素,基础safeHead
也会尝试遍历整个无限列表。
1 这样的功能实际上已经存在于Prelude中,它被称为listToMaybe