为什么不从前奏曲中“迭代”喜结良缘?

Why doesn't `iterate` from the Prelude tie the knot?

为什么 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 之类的东西都没有共享结构。

为清楚起见,我在下面包含的 Prelude 定义不需要调用映射的开销。

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

只是为了好玩,我制作了一个小程序来测试你的 iterate 与 Prelude 的对比 - 只是为了减少到正常形式 take 100000000 $ iterate (+1) 0(这是 Int 的列表).我只 运行 5 次测试,但你的版本 运行 为 7.833(最大 7.873 最小 7.667)而 Prelude 为 7.519(最大7.591 分钟 7.477)。我怀疑时差是 map 被调用的开销。

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

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

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