Haskell 为什么';前奏曲中的“迭代”打结?

Haskell 为什么';前奏曲中的“迭代”打结?,haskell,tying-the-knot,Haskell,Tying The Knot,为什么不定义为 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这样的东西中没有共享结构

为什么不定义为

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
这样的东西中没有共享结构。

为了清晰起见,我在下面包含了前奏曲定义,它不需要调用map

iterate f x =  x : iterate f (f x)
只是为了好玩,我制作了一个小的快速程序来测试你的
iterate
与前奏曲的对比-只是为了简化到正常形式
take 100000000$iterate(+1)0
(这是
Int
s的列表)。我只运行了5个测试,但是你的版本运行了
7.833
(max
7.873
min
7.667
),而前奏曲的测试是
7.519
(max
7.591
min
7.477
)。我怀疑时差是
map
被调用的开销


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

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


相比之下,Prelude的版本使用了
iterate
的调用框架,因为它只需要一次,一个好的编译器可以重用这一框架,并对其中的值进行变异,以实现更优化的整体操作(在这种情况下,列表的单元格彼此独立).

值得记住的是,您的链接是GHC的实现。您可以非常切实地编写一个编译器,以这种方式实现
迭代。GHC背后的人只是选择不这么做。谢谢。但是,例如,
iterate(0:)[]
中存在共享结构。这个
迭代是否正确地处理了这个问题?有时共享不是一种收益,而是一种损失,因为它会导致内存中的结构保留,否则可能会被丢弃(当只有一个消费者时,例如打印
)。如果是这种情况,递归比corecursion更可取。@WillNess
iterate
总是生成一个无限列表。它将始终依赖于corecursion。@user3237465是的,
iterate(0:)[]
共享尾部。不过,这与结婚无关。这只是因为在
iteratefx=x:iteratef(fx)
中,变量
x
在两种用途之间共享。