是否在列表推导中创建了任何中间数据结构

时间:2011-12-09 17:01:07

标签: haskell list-comprehension fold

似乎foldr与列表理解进行某种融合,因此与foldl(21mb)相比,它需要更少的内存(11mb)分配。

myfunc = sum $ foldr g acc [ f x | x <- xs ]
f x = ..
g x y = ..

任何人都可以解释如何以及为什么?另外,懒惰的评价对此有何帮助。

3 个答案:

答案 0 :(得分:8)

我们可以理解为map f xs。如果你正在编译这个,那么ghc应该能够将总和,折叠和地图融合成一个通道:http://www.haskell.org/haskellwiki/Correctness_of_short_cut_fusion。但即使你不是,懒惰也是你的内存使用的朋友。地图生成的列表是懒惰的 - f仅在需要时应用。只有当折叠者要求它时才需要f。而且由于你的折叠器显然正在产生另一个(懒惰)列表,所以折叠的每一步都只需要依次求和。因此,您仍然可以依次应用每个函数,但不需要同时生成完整的中间数据结构。虽然你已经编写了一整套函数组合,但评估模型将倾向于处理这组特定的代码,模拟一大堆挥手,有点像一个循环(尽管没有融合,有一个相当数量的循环)间接的。)

答案 1 :(得分:8)

左侧折叠在遍历整个列表之前不能产生任何输出(结果的一部分)。取决于你折叠的功能,可以构建一个大数据结构或一个大thunk,它使用大量内存(如果你在Int列表中折叠例如(+),它可以在常量内存中运行)

右侧折叠可以,对于适当的函数(这样可以产生[部分]结果而不检查第二个参数)递增地产生它们的结果,这样如果结果被适当地消耗并且输入列表被适当地生成,则整个计算可以在小的恒定空间中运行。正如sclv所说,在这些情况下,它基本上会减少到一个循环。

答案 2 :(得分:1)

这是GHC编译器的一个功能。基本上,GHC可以识别何时在“管道”中使用列表,并且可以将整个构造转换为C中的while - 循环的等价物,而不是根本不分配列表。

这与foldr而不是foldl一起使用的原因取决于您在示例中使用的函数g。由于foldrfoldl相对,因此累积了作为参数给出的函数的结果(又名:foldl需要整个列表才能真正开始评估函数g,因此它构建了一个巨大的“thunk”未评估函数和列表中的最终元素作为结果 - 这就是为什么它在这种情况下使用了更多的内存 - 而{{1} }一旦获得任何列表输入就可以开始评估foldr,它在其累加器中称为“strict”,并且编译器可以做出某些可以导致优化的假设。

例如,如果函数g产生一个列表值,它可以继续上述“管道”优化策略,基本上将g视为foldr和使整个构造(从列表生成到列表消耗)成为严格的循环。这是唯一可能的,因为map只为它所消耗的每个列表元素生成一个列表元素,foldr不保证这样做(特别是对于无限列表)。