为什么前奏曲的"迭代"不能喜结连理?



为什么iterate不是这样定义的

iterate :: (a -> a) -> a -> [a]
iterate f x = xs where xs = x : map f xs

在前奏中?

像这样喜结连理似乎不会增加分享。

对比:

cycle xs = let x = xs ++ x in x

在这里打结具有在内存中创建循环链表的效果。 x是它自己的尾巴。 这是一个真正的收获。

您建议的实现不会增加对朴素实现的共享。而且它首先没有办法这样做 - 无论如何,像iterate (+1) 0这样的东西没有共享结构。

您的版本中没有打结,它只是在生成的列表中保留一个档次的指针,以找到下一次迭代的输入值。这意味着在生成下一个单元格之前,无法对每个列表单元格进行 gc-ed。

相比之下,Prelude的版本使用iterate的调用帧,并且由于只需要一次,一个好的编译器可以重用该帧并改变其中的值,以实现更优化的整体操作(在这种情况下,列表的单元格彼此独立)。

为了清楚起见,我在下面包括了Prelude定义,它不需要调用map的开销。

iterate f x =  x : iterate f (f x)

只是为了好玩,我做了一个小的快速程序来测试你的iterate与前奏 - 只是为了简化到正常形式take 100000000 $ iterate (+1) 0(这是一个Int的列表)。我只运行了 5 次测试,但您的版本运行了 7.833(最多 7.873 分钟 7.667 ),而 Prelude 的版本在 7.519(最多 7.591 分钟 7.477 )。我怀疑时差是map被召唤的开销。

第二个原因很简单:可读性。

最新更新