Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/fsharp/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Recursion 为什么F#对堆栈大小施加一个较低的限制?_Recursion_F#_Tail Recursion - Fatal编程技术网

Recursion 为什么F#对堆栈大小施加一个较低的限制?

Recursion 为什么F#对堆栈大小施加一个较低的限制?,recursion,f#,tail-recursion,Recursion,F#,Tail Recursion,我想知道将F#中的递归深度限制在10000左右是否有根本原因,以及理想情况下如何避免该限制。我认为编写使用O(n)堆栈空间的代码是完全合理的,如果有人不同意,请解释他们为什么这样做,我将不胜感激。非常感谢。我在下面解释我的想法 我不认为有任何理由不允许堆栈增长,直到整个可用内存耗尽。这意味着无限递归需要更长的时间来注意,但这并不是说我们已经无法编写消耗无限内存的程序。我知道,使用连续化和尾部递归可以将堆栈使用率降低到O(1),但我并不认为一直这样做对我有什么好处。我也看不出知道函数何时可能需要处

我想知道将F#中的递归深度限制在10000左右是否有根本原因,以及理想情况下如何避免该限制。我认为编写使用O(n)堆栈空间的代码是完全合理的,如果有人不同意,请解释他们为什么这样做,我将不胜感激。非常感谢。我在下面解释我的想法

我不认为有任何理由不允许堆栈增长,直到整个可用内存耗尽。这意味着无限递归需要更长的时间来注意,但这并不是说我们已经无法编写消耗无限内存的程序。我知道,使用连续化和尾部递归可以将堆栈使用率降低到O(1),但我并不认为一直这样做对我有什么好处。我也看不出知道函数何时可能需要处理“大”输入(按照8位微控制器的标准)有什么帮助

我认为这从根本上不同于必须使用累积参数来避免二次时间行为。虽然这也涉及到对实现细节的担忧,并且不需要对“小”输入进行处理,但它也非常不同,因为编译器不能简单地自行解决问题。此外,它的不同之处在于,稍微复杂的O(n)代码,如果编写得很简单,就会变成O(n^2),它比简单、缓慢、易于阅读的版本更有用。相比之下,延续式代码的内存复杂度与相应的朴素版本完全相同,只是使用了不同类型的内存。在这个时代,编译器不应该让我担心这种事情

虽然我“更喜欢”一个理论上的理由来解释为什么不可能有一个深层次的堆栈,但我们也可以讨论实际方面。在我看来,堆栈在某种程度上比堆更有效地管理内存,因为它不需要垃圾收集,而且很容易释放?我甚至不确定我是否能看到允许深度堆叠有成本。诚然,操作系统需要留出足够的虚拟空间,以容纳每个线程堆栈在整个程序中一次可能要使用的所有内存。但那又怎样呢。这并不是说这样做我们可能会超出目前常见的48位限制,或者硬件制造商不能简单地将该限制提高到64位

这里没有那么多关于F#的具体内容。我希望同样的限制也适用于C#,并且不认为这在C#中有任何必要,尽管在以命令式风格编程时,在实践中显然不那么痛苦

非常感谢您的回复/评论


编辑:我写了以下答案的摘要。

我至少可以想到两个可能的原因:

(1) 在许多计算机体系结构上,如果不将堆栈移动到地址空间中的其他位置,则很难在运行时增加堆栈的可用大小。正如@svick在评论中指出的那样,32位Windows上的.NET将主线程的堆栈大小限制为1MB。在Windows上更改主线程的堆栈大小需要更改.EXE文件


(2) 编程错误导致的堆栈溢出比实际需要超过可用堆栈大小的代码引起的堆栈溢出要常见得多。有限的堆栈大小是捕获编程错误的非常有用的哨兵。在98%的情况下,如果允许堆栈增长到可用内存,那么当您耗尽可用内存时,您第一次发现编程错误。

您可以轻松避免这一限制:只需启动一个新线程,该线程的堆栈大小与您希望使用的堆栈大小相同。

理论上,任何事情都是可能的。您可以编写一个编译器,使用堆来管理传统上的“堆栈”

在实践中,性能(特别是对于“函数调用”等基础知识)很重要。我们已经为有限堆栈内存模型定制/优化了半个世纪的硬件和操作系统

在这个时代,编译器不应该让我担心这种事情

嗯。垃圾收集是一个巨大的胜利;管理所有内存是一项基本上不必要的工作,许多应用程序可以在这里为程序员的工作效率权衡一些性能。但我认为很少有人认为人类对堆栈/递归的管理是一件大事,即使在函数式语言中也是如此,因此,在我看来,让程序员摆脱困境的价值是微不足道的


请注意,特别是在F#中,您可以使用CPS工作流,将相当一部分堆栈转换为堆调用和/或尾部调用,如果您想实现这一点,只需对编程风格/语法进行相对较小的更改(参见示例)

我想这已经有了很多答案。以下是总结:

i) 没有人提出堆栈限制低于可用内存总量的根本原因

ii)我从中学到的最多的答案是布赖恩的(非常感谢)。我强烈推荐他链接的博客帖子,以及他的其他博客。我发现它比两本关于F#I的好书中的任何一本都更有信息。(话虽如此,在你使用他在表面上使用的“边际”一词之前,你可能应该先看看他在关于反变形的博客文章的第6部分中所说的实现尾部递归是多么简单:)

编辑:乔恩·哈洛普以微弱优势屈居第二。非常感谢

iii)Svick提出了一种将限值提高三个数量级的简单方法。非常感谢

iv)Delnan建议最好的做法是在任何地方都使用褶皱/贴图,并以尾部递归的方式定义它们。这当然是对列表的合理建议,但在遍历图时可能不太适用。不管怎样,非常感谢你的建议

v) 乔尔和