【问题标题】:Haskell: A stricter fold' with deepseqHaskell:使用 deepseq 进行更严格的折叠
【发布时间】:2012-06-21 01:50:10
【问题描述】:

页面Foldr Foldl Foldl' 讨论foldl',并将其定义为:

foldl' f z []     = z
foldl' f z (x:xs) = let z' = z `f` x 
                    in seq z' $ foldl' f z' xs

这样做是为了避免空间泄漏,即产生恒定大小结果的fold' 仅使用恒定空间。

但是,正如此处指出的那样,这不一定有效:

所涉及的seq 函数只评估最顶层的构造函数。如果累加器是一个更复杂的对象,那么fold' 仍然会构建未评估的 thunk。

显而易见的解决方案是将seq 更改为deepseq,如图所示(假设您正在使用NFData):

foldl_strict f z []     = z
foldl_strict f z (x:xs) = let z' = z `f` x 
                          in deepseq z' $ foldl_strict f z' xs

但我感觉这可能非常低效,因为每次通过循环都需要deepseq 遍历整个结构(除非编译器可以静态证明这不是必需的)。

然后我尝试了这个:

foldl_stricter  f z l      = deepseq z $ foldl_stricter' f z l
foldl_stricter' f z []     = z
foldl_stricter' f z (x:xs) = let z' = deepseq x $ z `f` x 
                             in seq z' $ foldl_stricter' f z' xs

但发现它有这个问题。当它应该返回 3 时,下面会失败。

foldl_stricter (\x y -> x + head y) 0 [[1..],[2..]]

所以fold_stricter 太严格了。该列表不必严格,防止空间泄漏重要的是累加器是严格的。 fold_stricter 走得太远,也使列表变得严格,导致上述失败。

这将我们带回到fold_strict。在大小为n 的数据结构上重复运行deepseq 是否需要O(n) 时间,还是第一次只需要O(n) 时间和之后的O(1)? (正如dbaupp 在他下面的comment 中建议的那样)

【问题讨论】:

  • 据我了解,deepseq 只遍历其第一个参数的结构,不需要重新计算表达式。例如。让x = 1 + 2y = 3 + x,然后如果x 已经deepseq'd,评估deepseq y .. 只需要遍历(+) 3 x:它不需要查看x 内部(除了从中获取值3),因为thunk 已经被强制执行。 (或者这不正确?)
  • @dbaupp:我同意这种情况,但是如果xInts 的大树会发生什么。我们deepseq 它评估所有Ints,但我们仍然有一棵(现在评估的)Ints 树。当我们再次deepseq 时,我们是否必须再次遍历整个树来检查这些 Int 是否被评估,或者我们是否在顶层知道该树已经被 deepseqed 了?

标签: haskell lazy-evaluation fold seq


【解决方案1】:

事实上,foldl 的两个实现有很大不同。不能保证f z x 需要完全遍历x 来计算它的答案,所以deepseq x (f z x) 可能会做不必要的工作;此外,即使x 被完全评估,也不能保证f z x 没有嵌套的thunk,所以let z' = deepseq x (f z x) in seq z' (foo z') 可能做的不够。

你所说的问题的正确解决方法是使用foldl'和一个严格的数据类型作为累加器类型;这样seq就只需要检查构造函数就知道整个结构体已经被完全求值了,反之强制构造函数会强制整个结构体被求值。

【讨论】:

  • 你能举一个折叠函数的例子吗?或者这个问题没有通用的解决方案,而是每次想要做这样的折叠时都必须定义一个新的严格累加器类型?
  • @Clinton 你是对的:你必须为你想要的每种不同类型的累加器创建一个不同的严格类型(或使用现有的严格类型)。这与惰性折叠所面临的情况完全相同:每种不同类型的累加器都必须创建新类型或重用现有(惰性)类型,所以我不认为这是一个反对意见。
  • 对不起,我还是有点糊涂。你能解释和/或举例说明为什么foldl_strict 可能做得不够吗?
  • @Clinton foldl_strict 总是做足够的工作。 foldl_stricter 可能做得不够。例如,考虑f z x = x : x : z,您的seq 只评估第一个: 而不是第二个。
  • 那么foldl_strict 是否等同于定义严格的累加器类型?如果不是,有什么不同?
猜你喜欢
  • 2021-10-02
  • 1970-01-01
  • 2015-08-30
  • 1970-01-01
  • 1970-01-01
  • 2014-12-14
  • 2014-02-12
  • 2014-02-13
  • 1970-01-01
相关资源
最近更新 更多