为什么不可能在foldl方面重新定义(实现)foldr

时间:2017-01-16 22:49:20

标签: haskell fold proof

我们有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}的同义词}})。我很困惑,试图理解严格性在这里发挥作用。有人可以扩展这个解释吗?

2 个答案:

答案 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