haskell中的循环列表和无限列表有什么区别?

haskell中的循环列表和无限列表有什么区别?,haskell,infinite,circular-list,Haskell,Infinite,Circular List,引用@dfeuer对这个问题的回答:,这表示使用循环列表“击败”了垃圾收集器,因为它必须保留分配的循环列表中您消耗的所有内容,直到您删除对列表中任何cons单元格的引用为止 显然,在Haskell中,循环列表和无限列表是两个独立的东西。这篇博客()说,如果您实现循环,如下所示: cycle xs = xs ++ cycle xs 它是一个无限列表,而不是循环列表。要使其循环,您必须像这样实现它(如Prelude源代码中所示): 这两种实现之间到底有什么区别?为什么如果在循环列表中的某个位置保留

引用@dfeuer对这个问题的回答:,这表示使用循环列表“击败”了垃圾收集器,因为它必须保留分配的循环列表中您消耗的所有内容,直到您删除对列表中任何cons单元格的引用为止

显然,在Haskell中,循环列表和无限列表是两个独立的东西。这篇博客()说,如果您实现
循环
,如下所示:

cycle xs = xs ++ cycle xs
它是一个无限列表,而不是循环列表。要使其循环,您必须像这样实现它(如Prelude源代码中所示):


这两种实现之间到底有什么区别?为什么如果在循环列表中的某个位置保留一个cons单元,垃圾收集器也必须在分配之前保留所有内容?

循环列表和无限列表在操作上不同,但在语义上不同

循环列表实际上是内存中的一个循环——想象一下,一个单链接列表的指针跟随一个循环——所以占用了恒定的空间。由于列表中的每个单元格都可以从任何其他单元格访问,因此保留任何一个单元格都会导致保留整个列表

当您对无限列表进行更多评估时,它将占用越来越多的空间。如果不再需要,早期的元素将被垃圾收集,因此处理它的程序仍然可以在恒定的空间中运行,尽管垃圾收集开销会更高。如果需要列表中较早的元素,例如,因为您保留了对列表头部的引用,那么在计算列表时,列表将消耗线性空间,并最终耗尽可用内存

这种差异的原因是,在没有优化的情况下,典型的Haskell实现(如GHC)会为一个值分配一次内存,如
cycle
的第二个定义中的
xs'
,但会为函数调用重复分配内存,如第一个定义中的
cycle xs


原则上,优化可能会将一个定义转换为另一个定义,但由于性能特征完全不同,在实践中不太可能发生这种情况,因为编译器通常对使程序表现更差相当保守。在某些情况下,由于前面提到的垃圾收集属性,循环变量会更糟糕。

区别完全在于内存表示。从语言语义的角度来看,它们是不可区分的——你无法编写一个能够区分它们的函数,因此你的两个版本的
cycle
被认为是同一个函数的两个实现(它们是参数到结果的完全相同的映射)。事实上,我不知道语言定义是否保证其中一个是循环的,另一个是无限的

但不管怎样,让我们展示ASCII艺术。周期性清单:

   +----+----+                 +----+----+
   | x0 |   ----->   ...   --->| xn |    |
   +----+----+                 +----+-|--+
     ^                                |
     |                                |
     +--------------------------------+
无限列表:

   +----+----+
   | x0 |   ----->  thunk that produces infinite list
   +----+----+
循环列表的问题是,从列表中的每个cons单元格都有一条路径,指向所有其他的和它本身。这意味着,从垃圾收集器的角度来看,如果一个cons单元是可访问的,那么所有cons单元都是可访问的。另一方面,在普通无限列表中,没有任何循环,因此从给定的cons单元只能到达其后续单元

请注意,无限列表表示法比循环表示法更强大,因为循环表示法仅适用于在一定数量的元素之后重复的列表。例如,所有素数的列表可以表示为无限列表,但不能表示为循环列表

还请注意,这种区别可以概括为两种不同的方式来实现
fix
功能:

fix, fix' :: (a -> a) -> a
fix  f = let result = f result in result
fix' f = f (fix' f)

-- Circular version of cycle:
cycle  xs = fix (xs++)

-- Infinite list version of cycle:
cycle' xs = fix' (xs++)
GHC库用于我的
fix
定义。GHC编译代码的方式意味着为
result
创建的thunk同时用作
f
应用程序的结果和参数。也就是说,thunk在被强制时,将调用
f
的目标代码,并将thunk本身作为其参数,并用结果替换thunk的内容

cycle xs = xs ++ cycle xs            -- 1
cycle xs = xs' where xs' = xs ++ xs' -- 2
这两种实现之间到底有什么区别

使用GHC,区别在于实现2创建了一个自引用值(
xs'
),而实现1只创建了一个恰好相同的thunk

为什么如果在循环列表中的某个位置保留一个cons单元,垃圾收集器也必须在分配之前保留所有内容

这也是GHC特有的。正如Luis所说,如果循环列表中有一个cons单元格的引用,那么只需遍历循环就可以访问整个列表。垃圾收集器是保守的,不会收集您仍然可以访问的任何内容



Haskell是纯粹的,重构是合理的。。。只有在不考虑内存使用(以及其他一些事情,如CPU使用和计算时间)的情况下。Haskell语言没有指定编译器应该如何区分#1和#2。GHC该实现遵循某些内存管理模式,这些模式是合理的,但并不立即明显。

与其他语言完全相同……我误读了一个关于骑自行车的人的坏笑话,并料想会发生……相关问题似乎指的是很长的周期<代码>循环通常对于短循环给出很好的结果,但是对于长循环,您使用它的方式将决定无限列表表示是否会起作用,或者您是否最好更改程序的结构。
fix'
可能被称为
\u Y
?因为这就是它的本质——Y combinator,通过复制数据来模拟共享。@这取决于你对术语“Y combinator”的确切定义。据我所知,它指的是最狭义的术语
cycle xs = xs ++ cycle xs            -- 1
cycle xs = xs' where xs' = xs ++ xs' -- 2