List 无限列表上的左右折叠

List 无限列表上的左右折叠,list,haskell,functional-programming,infinite,fold,List,Haskell,Functional Programming,Infinite,Fold,我对(国际海事组织的一本好书,没有异议)中的以下段落有异议: 一个很大的区别是对的 折叠可以在无限的列表上工作,而左边的列表不能!说实话 很明显,如果你在某个点上取一个无限列表,然后把它折叠起来 从右边开始,您将最终到达列表的开头。 然而,如果你在某一点上取一个无限列表,然后你尝试折叠 从左边往上爬,你永远不会到达终点 我就是不明白。如果你拿一个无限列表,试着从右边折叠它,那么你必须从无限开始,这是不可能发生的(如果有人知道一种语言,你可以这样做,请告诉:p)。至少,根据Haskell的实现,您

我对(国际海事组织的一本好书,没有异议)中的以下段落有异议:

一个很大的区别是对的 折叠可以在无限的列表上工作,而左边的列表不能!说实话 很明显,如果你在某个点上取一个无限列表,然后把它折叠起来 从右边开始,您将最终到达列表的开头。 然而,如果你在某一点上取一个无限列表,然后你尝试折叠 从左边往上爬,你永远不会到达终点

我就是不明白。如果你拿一个无限列表,试着从右边折叠它,那么你必须从无限开始,这是不可能发生的(如果有人知道一种语言,你可以这样做,请告诉:p)。至少,根据Haskell的实现,您必须从这里开始,因为在Haskell中,foldr和foldl不接受一个参数来确定它们应该在列表中的何处开始折叠

我同意引用iff foldr和foldl的参数,这些参数决定了它们应该在列表中的什么位置开始折叠,因为如果你从一个定义的索引开始向右折叠,它最终会终止,而从哪里开始向左折叠并不重要;你将朝着无限折叠。然而,foldr和foldl不接受这个论点,因此引文毫无意义。在Haskell中,无限列表的左折叠和右折叠都不会终止


我的理解是正确的还是遗漏了什么?

你的理解是正确的。我想知道作者是否在谈论Haskell的惰性评估系统(在该系统中,您可以将一个无限列表传递给不包括fold的各种函数,它只评估返回答案所需的量)。但我同意你的观点,作者在这一段中描述的东西做得不好,而且它说的是错误的。

这里的关键是懒惰。如果您用于折叠列表的函数是严格的,那么在给定无限列表的情况下,左折叠和右折叠都不会终止

Prelude> foldr (+) 0 [1..]
^CInterrupted.
但是,如果您尝试折叠一个不太严格的函数,则可能会得到终止结果

Prelude> foldr (\x y -> x) 0 [1..]
1
您甚至可以得到一个无限数据结构的结果,因此,虽然它在某种意义上没有终止,但它仍然能够生成一个可以延迟使用的结果

Prelude> take 10 $ foldr (:) [] [1..]
[1,2,3,4,5,6,7,8,9,10]
但是,这不适用于
foldl
,因为您将永远无法计算最外层的函数调用,不管它是否懒惰

Prelude> foldl (flip (:)) [] [1..]
^CInterrupted.
Prelude> foldl (\x y -> y) 0 [1..]
^CInterrupted.
请注意,左折叠和右折叠之间的关键区别不是遍历列表的顺序(总是从左到右),而是结果函数应用程序的嵌套方式

  • 使用
    foldr
    ,它们嵌套在“内部”

    这里,第一次迭代将产生
    f
    的最外层应用程序。因此,
    f
    有机会变得懒惰,因此第二个参数不是总是被计算的,或者它可以在不强制第二个参数的情况下生成数据结构的某些部分

  • 使用
    foldl
    ,它们嵌套在“外部”

    在这里,我们只有在到达
    f
    的最外层应用程序后才能对任何内容进行求值,而在无限列表的情况下,无论
    f
    是否严格,我们都永远无法到达该应用程序


记住,在Haskell中,由于延迟计算,您可以使用无限列表。因此,
head[1..]
仅为1,
head$map(+1)[1..]
为2,即使“[1..]无限长。如果你不明白,停下来玩一会儿。如果你明白了,请继续阅读


我认为你的困惑部分在于
foldl
foldr
总是从一边或另一边开始,因此你不需要给出长度

foldr
有一个非常简单的定义

 foldr _ z [] = z
 foldr f z (x:xs) = f x $ foldr f z xs
为什么这可能会在无限列表上终止呢

 dumbFunc :: a -> b -> String
 dumbFunc _ _ = "always returns the same string"
 testFold = foldr dumbFunc 0 [1..]
这里我们将进入
foldr
a”“(因为值并不重要)和无限自然数列表。这会终止吗?对

它终止的原因是因为Haskell的计算相当于惰性项重写

所以

变成(允许模式匹配)

这与(根据我们对褶皱的定义)相同

现在通过
dumbFunc
的定义,我们可以得出

 testFold = "always returns the same string"
当我们有函数做一些事情,但有时是懒惰的时候,这就更有趣了。比如说

foldr (||) False 
用于查找列表是否包含任何
True
元素。我们可以使用它来定义高阶函数n
any
,它返回
True
当且仅当传递的函数对于列表中的某些元素为True时

any :: (a -> Bool) -> [a] -> Bool
any f = (foldr (||) False) . (map f)
惰性求值的好处在于,当遇到第一个元素
e
时,它将停止,从而
fe==True

另一方面,
foldl
的情况并非如此。为什么?一个非常简单的foldl

foldl f z []     = z                  
foldl f z (x:xs) = foldl f (f z x) xs
现在,如果我们尝试上面的例子,会发生什么

testFold' = foldl dumbFunc "" [1..]
testFold' = foldl dumbFunc "" (1:[2..])
这现在变成:

testFold' = foldl dumbFunc (dumbFunc "" 1) [2..]
所以

等等等等。我们永远不会有任何进展,因为Haskell总是首先计算最外层的函数(简而言之就是惰性计算)

这样做的一个很酷的结果是,您可以在
foldr
之外实现
foldl
,但反之亦然。这意味着从某种意义上讲,
foldr
是所有高阶字符串函数中最基本的函数,因为它是我们用来实现几乎所有其他函数的函数。有时您可能仍然希望使用
foldl
,因为您可以递归地实现
foldl
尾部,并从中获得一些性能增益

关键短语是“在某个时刻”

如果你在某一点上取一个无限列表,然后从右边折叠起来,你最终会得到一个无限列表
foldr (||) False 
any :: (a -> Bool) -> [a] -> Bool
any f = (foldr (||) False) . (map f)
foldl f z []     = z                  
foldl f z (x:xs) = foldl f (f z x) xs
testFold' = foldl dumbFunc "" [1..]
testFold' = foldl dumbFunc "" (1:[2..])
testFold' = foldl dumbFunc (dumbFunc "" 1) [2..]
testFold' = foldl dumbFunc (dumbFunc (dumbFunc "" 1) 2) [3..]
testFold' = foldl dumbFunc (dumbFunc (dumbFunc (dumbFunc "" 1) 2) 3) [4..]
testFold' = foldl dumbFunc (dumbFunc (dumbFunc (dumbFunc (dumbFunc "" 1) 2) 3) 4) [5..]
foldr (:) z (1:[2..])         ==> (:) 1 (foldr (:) z [2..])
1 : foldr (:) z (2:[3..])     ==> 1 : (:) 2 (foldr (:) z [3..])
1 : 2 : foldr (:) z (3:[4..]) ==> 1 : 2 : (:) 3 (foldr (:) z [4..])
1 : 2 : 3 : ( lazily evaluated thunk - foldr (:) z [4..] )