一般来说,是否可以在JavaScript中使用手动堆栈来转换递归函数?
注意以下功能:一般来说,是否可以在JavaScript中使用手动堆栈来转换递归函数?,javascript,recursion,Javascript,Recursion,注意以下功能: function count(n) { if (n === 0) { return 0; } else { return 1 + count(n - 1); } } 它是从0到N计数的最简单的递归函数。由于JavaScript的堆栈限制很小,因此该函数很容易溢出。一般来说,任何递归函数都可以转换为使用手动堆栈的函数,因此不会出现堆栈溢出;但这样做很复杂。在一般情况下,是否可以将JavaScript递归函数转换为使用自己堆栈的函数,而不使用延续传递样式
function count(n) {
if (n === 0) {
return 0;
} else {
return 1 + count(n - 1);
}
}
它是从0
到N
计数的最简单的递归函数。由于JavaScript的堆栈限制很小,因此该函数很容易溢出。一般来说,任何递归函数都可以转换为使用手动堆栈的函数,因此不会出现堆栈溢出;但这样做很复杂。在一般情况下,是否可以将JavaScript递归函数转换为使用自己堆栈的函数,而不使用延续传递样式?换句话说,假设我们写了:
const count = no_overflow(function(count) {
return function(n) {
if (n === 0) {
return 0;
} else {
return 1 + count(n - 1);
}
}
});
是否可以实现无溢出
,使新的计数
函数与旧函数等效,除非没有堆栈溢出
笔记:
no_overflow
应该适用于非尾部递归函数yield
编写函数也不起作用,原因类似:您不能从内部lambda编写yield
无溢出
基本上可以像无堆栈Y组合器一样工作在JavaScript中,调用函数
f(x,y,…)
会让我们了解堆栈和帧的底层实现细节。若您再次使用函数应用程序,您将绝对不可避免地遇到堆栈溢出
然而,如果我们可以采用稍微不同的表示法,例如调用(f,x,y,…)
,我们就可以随心所欲地控制函数应用程序-
const add1 = x =>
x + 1
const count = (n = 0) =>
n === 0
? 0
: call(add1, call(count, n - 1)) // <-- count not in tail pos
console.log(noOverflow(count(99999)))
// 99999
不奇怪,这是一个非平凡的问题,但答案应该有助于详细的事情,你必须考虑和一些好的测试用例,你应该选择实现一个自己的解决方案。
展开下面的代码段以验证浏览器中的结果-
const call=(f,…值)=>
({type:call,f,values})
常量重复=(…值)=>
({type:recur,values})
常量标识=x=>
x
常量循环=(f)=>
{const aux1=(expr={},k=identity)=>
expr.type==重现
调用(aux,expr.values,values=>call(aux1,f(…值),k))
:expr.type==调用
调用(aux,expr.values,values=>call(aux1,expr.f(…values),k))
:调用(k,expr)
常量aux=(表达式=[],k)=>
呼叫
(exprs.reduce)
((mr,e)=>
k=>call(mr,r=>call(aux1,e,x=>call(k,[…r,x]))
,k=>调用(k,[])
)
K
)
返回运行(aux1(f())
}
const run=r=>
{while(r&&r.type==调用)
r=r.f(…r.values)
返回r
}
const noOverflow=t=>
循环(=>t)
常数add1=x=>
x+1
常数计数=(n=0)=>
n==0
? 0
:call(add1,call(count,n-1))
console.log(noOverflow(count(99999)))
//99999
这可能会引起兴趣:@Thankyou我在他的评论后补充说,撇开堆栈溢出和相关问题不谈:一种语言即使没有递归函数也可以图灵完全,因此递归函数可以实现的一切,迭代函数也可以code@user120242这并不总是关于什么是可能的,有时是关于如何表达解决方案的问题。我赞成,但请注意,在某些情况下并非如此。例如,是一个库,它可以将几乎任何递归函数转换为使用手动堆栈的函数。但是,如果在lambda中进行递归,它将不起作用。我的观点是,我们可能缺少一些独创的解决方案;如果不是,我希望看到一个证据,证明这是根本不可能的。我最近在这里看到了一个引人注目的模型:-但它需要尾部递归函数和对堆栈和帧指针的一流访问,而不是JS提供的。这是我最喜欢学习的主题之一,所以我很想看到你在攻克坚果方面取得的任何进展!阅读链接的github repo now:)repo并不是真正用来解决这个问题的,但我感兴趣的是创建对角下降的递归函数(这样它们就不会陷入无限循环中)。但是它间接地解决了这个问题,除了lambdas中有递归调用的函数之外。如果没有你所描述的某种外部堆栈和指针操作,这听起来是一个不可能解决的问题):Fwiw,在链接问答的第3部分中,我尝试具体化延续,使之能够在循环中执行callcc
或使用shift
/reset
等提示。尽管我仍然坚持让续集在所有场景中都100%安全。
const noOverflow = t =>
loop(_ => t)