页面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
。在大小为deepseq
的数据结构上重复运行n
需要花费O(n)
时间,或者第一次O(n)
时间以及此后O(1)
? (正如dbaupp在他的comment中建议的那样)
答案 0 :(得分:3)
事实上,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
只需检查构造函数就知道整个结构已被完全评估,反之强制构造函数将强制整个结构被完全评估。