【发布时间】: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 + 2和y = 3 + x,然后如果x已经deepseq'd,评估deepseq y ..只需要遍历(+) 3 x:它不需要查看x内部(除了从中获取值3),因为thunk 已经被强制执行。 (或者这不正确?) -
@dbaupp:我同意这种情况,但是如果
x是Ints 的大树会发生什么。我们deepseq它评估所有Ints,但我们仍然有一棵(现在评估的)Ints 树。当我们再次deepseq时,我们是否必须再次遍历整个树来检查这些 Int 是否被评估,或者我们是否在顶层知道该树已经被deepseqed 了?
标签: haskell lazy-evaluation fold seq