为什么foldright适用于无限列表?

时间:2018-02-07 00:50:03

标签: haskell functional-programming language-agnostic fold

我的印象是foldright从列表的末尾开始并向后工作(这就是我想象的右关联意味着什么)。所以我很困惑,以下内容适用于无限列表。

我有一个函数find

find :: (a -> Bool) -> List a -> Optional a
find p = foldRight (\c a -> if p c then Full c else a) Empty

请注意以下工作:

>> find (const True) infinity
Full 0

我做了一些搜索,发现了这篇文章:How do you know when to use fold-left and when to use fold-right?

不幸的是,接受的答案并不是特别有用,因为右关联操作的例子是:

A x (B x (C x D))

这仍然意味着它需要首先执行最正确的事情。

我想知道是否有人可以为我解决这个问题,谢谢。

3 个答案:

答案 0 :(得分:11)

让我们从一个函数开始:

>>> let check x y = if x > 10 then x else y
>>> check 100 5
100
>>> check 0 5
5

check有两个参数,但可能不会使用它的第二个参数。由于haskell是惰性的,这意味着可能永远不会评估第二个参数:

>>> check 20 (error "fire the missles!")
20

这种懒惰使我们可以跳过可能无限的工作量:

>>> check 30 (sum [1..])
30

现在让我们使用等式推理逐步完成foldr check 0 [0..]

foldr check 0 [0..]
= check 0 (foldr check 0 [1..]) -- by def'n of foldr
= foldr check 0 [1..] -- by def'n of check
= check 1 (foldr check 0 [2..]) -- by def'n of foldr
= foldr check 0 [2..] -- by def'n of check
-- ...
= foldr check 0 [10..]
= check 10 (foldr check 0 [11..]) -- by def'n of foldr
= foldr check 0 [11..] -- by def'n of check
= check 11 (foldr check 0 [12..]) -- by def'n of foldr
= 11 -- by def'n of check

注意懒惰如何强迫我们从上到下评估,看看最外层函数调用如何(以及如果)使用其参数,而不是自下而上(在将它们传递给函数之前评估所有参数) ,就像严格的语言一样。

答案 1 :(得分:4)

它的作用是因为懒惰的评价。我们来看一个非常简单的例子。

import Data.Char (toUpper)

main :: IO ()
main = interact (foldr capitalized []) where
  capitalized :: Char -> String -> String
  capitalized x xs = (toUpper x):xs

以交互方式运行此程序,看看会发生什么。输入是从标准输入读取的无限(或至少是无限期)字符列表。

这是有效的,因为输出列表的每个元素在需要时都会延迟生成。因此尾部首先:它只在需要时才计算。在此之前,它已推迟,我们可以使用部分结果。 'h':xs的部分结果是'H':(foldr capitalized [] xs)'h':'e':'l':'l':'o':',':' ':'w':'o':'r':'l':'d':'!':'\n':xs的部分结果是我们在进入尾部xs之前可以输出的字符串。

现在看看如果您使用foldl尝试此操作会发生什么。

这适用于生成有用前缀的任何数据结构。对于产生单个值的减少操作,并且没有有用的中间结果,严格的左折(Data.List.foldl')通常是更好的选择。

答案 2 :(得分:1)

您的异议证明太多了。如果它是有效的,根本不可能有无限列表!使用(:)构造无限列表。它的第二个参数,即列表的尾部,也是一个无限列表,必须先进行评估。这递归不会让我们到处都是。