List 无限列表上的左右折叠
我对(国际海事组织的一本好书,没有异议)中的以下段落有异议: 一个很大的区别是对的 折叠可以在无限的列表上工作,而左边的列表不能!说实话 很明显,如果你在某个点上取一个无限列表,然后把它折叠起来 从右边开始,您将最终到达列表的开头。 然而,如果你在某一点上取一个无限列表,然后你尝试折叠 从左边往上爬,你永远不会到达终点 我就是不明白。如果你拿一个无限列表,试着从右边折叠它,那么你必须从无限开始,这是不可能发生的(如果有人知道一种语言,你可以这样做,请告诉:p)。至少,根据Haskell的实现,您必须从这里开始,因为在Haskell中,foldr和foldl不接受一个参数来确定它们应该在列表中的何处开始折叠 我同意引用iff foldr和foldl的参数,这些参数决定了它们应该在列表中的什么位置开始折叠,因为如果你从一个定义的索引开始向右折叠,它最终会终止,而从哪里开始向左折叠并不重要;你将朝着无限折叠。然而,foldr和foldl不接受这个论点,因此引文毫无意义。在Haskell中,无限列表的左折叠和右折叠都不会终止List 无限列表上的左右折叠,list,haskell,functional-programming,infinite,fold,List,Haskell,Functional Programming,Infinite,Fold,我对(国际海事组织的一本好书,没有异议)中的以下段落有异议: 一个很大的区别是对的 折叠可以在无限的列表上工作,而左边的列表不能!说实话 很明显,如果你在某个点上取一个无限列表,然后把它折叠起来 从右边开始,您将最终到达列表的开头。 然而,如果你在某一点上取一个无限列表,然后你尝试折叠 从左边往上爬,你永远不会到达终点 我就是不明白。如果你拿一个无限列表,试着从右边折叠它,那么你必须从无限开始,这是不可能发生的(如果有人知道一种语言,你可以这样做,请告诉:p)。至少,根据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
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
元素。我们可以使用它来定义高阶函数nany
,它返回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..] )