foldl与foldr行为与无限的清单
-
28-09-2019 - |
题
代码myAny功能 这个问题 使用foldr.它会停止处理的一个无限的清单时所谓被满足。
我重写了使用foldl:
myAny :: (a -> Bool) -> [a] -> Bool
myAny p list = foldl step False list
where
step acc item = p item || acc
(注意,该论点的步骤的功能是正确的逆转。)
然而,它不再会停止处理无限的清单。
我尝试跟踪功能的执行,因为在 Apocalisp的答案:
myAny even [1..]
foldl step False [1..]
step (foldl step False [2..]) 1
even 1 || (foldl step False [2..])
False || (foldl step False [2..])
foldl step False [2..]
step (foldl step False [3..]) 2
even 2 || (foldl step False [3..])
True || (foldl step False [3..])
True
然而,这不是方式的功能的行为。这怎么是错的?
解决方案
如何 fold
s差异似乎是一个频繁的来源的混淆,这是更一般性的概述:
考虑折叠清单的n值 [x1, x2, x3, x4 ... xn ]
与某些功能 f
和种子 z
.
foldl
为:
- 左结合:
f ( ... (f (f (f (f z x1) x2) x3) x4) ...) xn
- 尾递归:它访问过该列表,生产的价值之后
- 懒惰:没有什么是评估直到的结果是需要
- 倒退:
foldl (flip (:)) []
反转列表。
foldr
为:
- 正确的关联:
f x1 (f x2 (f x3 (f x4 ... (f xn z) ... )))
- 递归入一个论点:每次迭代的适用
f
到下一个的价值和结果的折其余的清单。 - 懒惰:没有什么是评估直到的结果是需要
- 转发:
foldr (:) []
返回一个名单保持不变。
还有一种稍微妙的一点在这里旅行的人有时:因 foldl
是 倒退 每个应用程序的 f
加入 外面 的结果;因为它是 懒惰, 没有什么是评估直到结果是必需的。这意味着计算的任何部分的结果,Haskell的第一迭代过 整个列表 建设一个表达的嵌套功能的应用程序,然后评估 外面 功能,评估其参数的需要。如果 f
总是使用它的第一个参数,这意味着哈斯克尔已recurse到最里面的词,然后向后工作的计算每个应用程序的 f
.
这显然是一个远离有效的尾递归功能最程序员知道和喜爱!
事实上,即使 foldl
在技术上是尾递归的,因为整个结果表达的是建立之前,评估任何东西, foldl
可以引起一堆溢出!
另一方面,考虑 foldr
.这也是偷懒,而是因为它运行 转发, 每个应用的 f
加入 内部 结果。因此,为计算的结果,Haskell的构造 单 功能的应用,第二个参数,这是休息的折叠清单。如果 f
是偷懒,在其第二个参数--的数据的构造,例如--的结果将 逐步懒, 与每个步骤的倍计算,只有当某些部分的结果需要进行评估。
所以我们可以看到为什么 foldr
有时工作上无限的清单时 foldl
不:前者可以延迟转换成一个无限的清单纳入的另一个懒惰无限的数据结构,而后者则必须在检查整个清单产生的任何部分的结果。另一方面, foldr
用一个功能需要这两个论点立即的,例如 (+)
, ,工作(或者说,没有工作)很像 foldl
, ,建立一个巨大的表达之前评估它。
所以两个要点需要注意的是这些:
foldr
可以改变一个懒惰的递数据结构到另一个。- 否则,懒惰的褶皱将崩溃,一堆溢出或大或无限的清单。
你可能已经注意到,这听起来像 foldr
可以做的一切 foldl
可以,加上更多。这是真的!事实上, foldl几乎是没用的!
但是,如果我们要产生一个非懒惰的结果通过折叠在一个大(但不是无限的)单吗?为此,我们想要一个 严格的折, , 标准图书馆提供thoughfully:
foldl'
为:
- 左结合:
f ( ... (f (f (f (f z x1) x2) x3) x4) ...) xn
- 尾递归:它访问过该列表,生产的价值之后
- 严格:每个功能应用程序进行评估沿途
- 倒退:
foldl' (flip (:)) []
反转列表。
因 foldl'
是 严格, ,来计算的结果Haskell会 评估 f
在每一个步骤,而不是让左边的参数积累巨大的、尚未表达。这给了我们通常的、有效的尾递归我们想要的!换句话说:
foldl'
可以折叠大名单的效率。foldl'
挂在一个死循环(不引起一堆溢)在一个无限的清单。
Haskell wiki有 一个页面讨论这个, 为。
其他提示
myAny even [1..]
foldl step False [1..]
foldl step (step False 1) [2..]
foldl step (step (step False 1) 2) [3..]
foldl step (step (step (step False 1) 2) 3) [4..]
等等
直觉, foldl
始终在“外部”或“左”上,因此首先扩展。 ad infinitum。
您可以在Haskell的文档中看到 这里 该折叠是尾部回复的,如果通过无限列表,将永远不会结束,因为它在返回值之前就在下一个参数上调用自己...
我不知道haskell,但是在计划中, fold-right
首先将始终在列表的最后一个元素上“行动”。因此,对于循环列表不起作用(与无限列表相同)。
我不确定是否 fold-right
可以写入尾部回复,但是对于任何循环列表,您都应该获得堆栈溢出。 fold-left
OTOH通常是通过尾随的递归实施的,如果不尽早终止它,只会陷入无限的循环中。