递归运行时实现Java与其他/函数语言?

递归运行时实现Java与其他/函数语言?,java,programming-languages,recursion,functional-programming,Java,Programming Languages,Recursion,Functional Programming,我喜欢递归,但在Java中,您在某个点上遇到了死胡同。例如,我遇到过这样一种情况,即迭代次数约为100K的递归无法工作(StackOverflowerr)。糟糕的是,由于这个运行时堆栈限制的原因,我不得不切换到恼人的“命令循环” 我想知道其他(特别是函数式)语言如何在运行时绕过堆栈溢出?我想特别是函数式语言运行时更好地处理这个问题,因为递归是核心概念 有人提供了一些信息或外部资源吗?大多数语言都对编译器进行了优化。尾部递归意味着递归调用应该是递归方法的最后一个调用。然后,编译器可以将其优化为循环

我喜欢递归,但在Java中,您在某个点上遇到了死胡同。例如,我遇到过这样一种情况,即迭代次数约为100K的递归无法工作(StackOverflowerr)。糟糕的是,由于这个运行时堆栈限制的原因,我不得不切换到恼人的“命令循环”

我想知道其他(特别是函数式)语言如何在运行时绕过堆栈溢出?我想特别是函数式语言运行时更好地处理这个问题,因为递归是核心概念


有人提供了一些信息或外部资源吗?

大多数语言都对编译器进行了优化。尾部递归意味着递归调用应该是递归方法的最后一个调用。然后,编译器可以将其优化为循环,防止发生堆栈溢出错误

我不确定是否所有的
javac
实现都实现了尾部递归。这不是规范所要求的。然而,对于任何递归程序来说,它都是一种重要的优化技术,因此我认为主流实现确实提供了尾部递归

您可以自己测试这一点,方法是使用一个(简单的)非尾部递归程序生成
StackOverflowError
,并使其尾部递归(例如,计算a)


编辑:如用户sje397的评论所示,以前Java中有过一个错误。还可以看看这个问题,它提供了一些附加信息。

您可以通过以下方法增加java堆栈大小:

java -Xss1024k MyProgram
(将java的堆栈大小增加到1024KB。) 但一般来说,使用深度递归不是一个好主意。试着做一个迭代的解决方案。

不只是在Java中,应该避免使用“具有~100K次迭代的递归”。它只在尾部调用优化时起作用,这并不总是可能的。因此,最好首先不要养成过度递归的习惯


递归是人们在第一次学习时往往过度使用的概念之一,因为不到处炫耀似乎太酷了……

下面是@Ronald Wildenberg的答案:

我不确定是否所有javac实现都实现了尾部递归。这不是规范所要求的

简单的回答是他们不支持

较长的答案是,由于JVM的设计,尾部递归优化在Java中是一个棘手的问题。John Rose@Oracle在书中谈到了这个问题。这篇博客文章的主旨是提出一个字节码扩展方案,以支持“硬”尾部调用。但最后一段暗示了为什么实现“软”(即透明)尾部调用很难。尾部调用优化会干扰JVM捕获堆栈跟踪的能力,这对Java安全体系结构有“影响”

这提供了有关该问题的更多细节。也请阅读评论


在当前JVM上实现尾部递归的方法似乎是在编译器前端实现它。显然Scala做到了这一点。

正如其他人所提到的,支持适当的尾部递归有帮助。但它本身并不足以支持深度递归。不管BLUB程序员怎么想,深度递归自然适合某些任务,比如处理深度递归数据结构

支持深层(非尾部)递归的策略通常包含在支持一级延续的策略中。你可能会发现(Clinger等人)很有趣

我脑子里想了几个策略:

  • CP转换程序或以其他方式堆分配连续帧(缺点:失去堆栈的一些性能优势)
  • 在堆栈溢出时,将堆栈复制到单独的内存块中,并将堆栈指针重置为基址;在下溢时,将旧堆栈复制回
  • 在堆栈溢出时,从堆中分配一个新的堆栈区域,并在那里重置堆栈指针;在下溢时,返回到旧堆栈的末尾
  • 在堆栈溢出时,创建一个新线程以继续运行计算,并等待线程的结果(缺点:创建线程可能很昂贵(但工作线程可以缓存),并且跨线程移动计算可能会导致线程本地存储发生havok等)

我认为这可能有助于了解java的情况。这个问题是关于递归的另一个好问题。我从来没有听说java编译器优化尾部递归。但scala编译器确实如此。我同意,递归并不是所有问题的解决方案,我只是想知道语言运行时是如何解决大数据的。阅读答案尾部递归是唯一的优化。你说尾部递归不总是可能的这意味着当面对更大的数据集时,你通常必须更改算法。@manuel:最优秀/有用的递归算法做了一些分而治之的事情,将最大递归深度限制为log(n),这意味着它实际上不会成为问题(假设单个递归步骤不需要太多额外内存)。“~100K次迭代的递归”是应该避免的事情,不仅仅是在Java中。在递归是唯一迭代手段的语言中,这是很难避免的。但当然,这些语言保证尾部递归得到优化——你只需要确保你的函数是尾部递归的。“10万次迭代的递归”本质上没有错,除了不能处理它的语言之外。有许多算法可以用递归更清晰地表达。一种语言不以这种方式表达它们的能力是该语言的一个限制(尽管很多时候是出于完全合理的原因)对于这些语言,一般有办法解决问题,利用他们所获得的能力,放弃处理能力。谢谢PAR。