为什么`foldl1`内存不足但'foldr1`工作正常?

时间:2013-04-30 19:16:41

标签: haskell

我使用lastfoldl1撰写了foldr1函数。

lastr :: [a] -> a
lastr = foldr1 (flip const)

lastl :: [a] -> a
lastl = foldl1 (flip const)

它们只适用于短名单。但是当我尝试使用很长的列表[1..10 ^ 8]时,lastr在6.94秒内返回解决方案,但是lastl内存不足。

foldr1和foldl1的定义是(根据我的理解)

foldr1 f [x] = x
foldr1 f (x:xs) = f x $ foldr1 f xs

foldl1 f [x] = x
foldl1 f (x:y:ys)=foldl1 f $ f x y : ys

从这些看来,foldl1似乎比foldr1使用更少的内存,因为foldr1需要保持像f x1 $ f x2 $ f x3 $ f x4 $...这样的表达式,而foldl1每次只能计算f x y并将其存储为列表的头元素而不是持有它直到达到10 ^ 8。

有谁能告诉我我的论点有什么问题?

1 个答案:

答案 0 :(得分:19)

如果组合函数在其第二个参数中是惰性的,则右侧折叠可以立即开始生成。一个简单的例子:

foldr1 (++) ["one", "two", "three", ...]
    ~> "one" ++ foldr1 (++) ["two", "three", ...]

并且可以立即访问结果的第一部分,而无需进一步评估(++)的第二个参数。只需在消耗第一部分时评估。通常,第一部分可能已经被垃圾收集。

f = flip const作为组合函数的示例中,我们有不同的情况,即第二个参数中的严格(1),但不需要在所有。它忽略了它的第一个。这对右折也有好处。在这里

foldr1 f [x1, x2, x3, ... ]
    ~> f x1 (foldr1 f [x2, x3, ... ])

现在可以立即评估最外面的f

    ~> foldr1 f [x2, x3, ... ]
    ~> f x2 (foldr1 f [x3, ... ])
    ~> foldr1 f [x3, ... ]

并且在每一步中,最外面的f总是可以立即被评估(完全),并且一个列表元素被丢弃。

如果列表是由生成器给出的,可以在按顺序使用时在恒定空间中创建它,

last = foldr1 (flip const)

可以在恒定的空间内运行。

左侧折叠,情况有所不同。因为这是尾递归的

foldl1 f (x:y:zs) = foldl f x (y:zs) = foldl f (f x y) zs

在折叠到达列表末尾之前,它无法返回任何内容。特别是,左侧折叠永远不会终止于无限列表。

现在,查看我们的案例f = flip const,我们找到了

foldl1 f [x1, x2, x3, x4, ...]
    ~> foldl f x1 [x2, x3, x4, ... ]
    ~> foldl f (f x1 x2) [x3, x4, ... ]
    ~> foldl f (f (f x1 x2) x3) [x4, ... ]

当然可以立即评估f x1 x2x2,然后评估f x2 x3 = x3,但这仅适用于此特殊f

由于foldl是一般的高阶函数,它不能在需要之前评估中间结果,因为中间结果可能永远不需要 - 实际上,这里从不需要它们,在列表的末尾,有一个表达式

f (f (f (f ...y3) y2) y1) y0
   ~> y0

然后可以在不查看构建第一个参数的大量嵌套f的情况下评估最外层的f

foldl(分别为foldl1)无法知道立即评估中间结果会更有效率。

严格的左侧折叠,foldl'foldl1'这样做,它们将中间结果评估为弱头正常形式(到最外面的值构造函数或lambda),并且

last = foldl1' (flip const)

也非常有效。

但是,由于中间结果的评估比foldr更进一步,因此它们的效率会低一些,而且重要的是,如果任何列表元素是,则foldl1'版本会返回

foldl1' f [x1, ⊥, x3, x4]
    ~> foldl' f x1 [⊥, x3, x4]
    ~> case f x1 ⊥ of
          pattern    -- that causes ⊥
   ~> ⊥

foldr1版本没有问题,因为它根本不检查列表元素或中间结果。


(1) f在第二个参数中是严格的意味着

f x ⊥ = ⊥

由于f只返回其第二个参数,显然就是这种情况。