Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/javascript/394.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
一般来说,是否可以在JavaScript中使用手动堆栈来转换递归函数?_Javascript_Recursion - Fatal编程技术网

一般来说,是否可以在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)