Haskell:一个更严格的折叠'与​​deepseq

时间:2012-06-19 03:15:53

标签: haskell lazy-evaluation fold seq

页面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中建议的那样)

1 个答案:

答案 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只需检查构造函数就知道整个结构已被完全评估,反之强制构造函数将强制整个结构被完全评估。