递归运行时实现Java与其他/函数语言?
我喜欢递归,但在Java中,您在某个点上遇到了死胡同。例如,我遇到过这样一种情况,即迭代次数约为100K的递归无法工作(StackOverflowerr)。糟糕的是,由于这个运行时堆栈限制的原因,我不得不切换到恼人的“命令循环” 我想知道其他(特别是函数式)语言如何在运行时绕过堆栈溢出?我想特别是函数式语言运行时更好地处理这个问题,因为递归是核心概念递归运行时实现Java与其他/函数语言?,java,programming-languages,recursion,functional-programming,Java,Programming Languages,Recursion,Functional Programming,我喜欢递归,但在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等)