简体   繁体   English

NodeJS中的尾递归

[英]Tail recursion in NodeJS

So I recently came across case I needed to write code where callback calls itself and so on and wondered about NodeJS and tail-call support, so I found this answer https://stackoverflow.com/a/30369729 saying that yup, it's supported. 所以我最近遇到的情况我需要编写回调调用自己的代码等等,并想知道NodeJS和尾调用支持,所以我发现这个答案https://stackoverflow.com/a/30369729说是的,它支持。

So I tried it with this simple code: 所以我尝试使用这个简单的代码:

"use strict";
function fac(n){
    if(n==1){
        console.trace();
        return 1;
    }
    return n*fac(n-1);
}

fac(5);

Using Node 6.9.2 on Linux x64 and run it as node tailcall.js --harmony --harmony_tailcalls --use-strict and result was: 在Linux x64上使用Node 6.9.2并将其作为node tailcall.js --harmony --harmony_tailcalls --use-strict ,结果如下:

Trace
    at fac (/home/tailcall.js:4:11)
    at fac (/home/tailcall.js:7:11)
    at fac (/home/tailcall.js:7:11)
    at fac (/home/tailcall.js:7:11)
    at fac (/home/tailcall.js:7:11)
    at Object.<anonymous> (/home/tailcall.js:10:1)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)

Which clearly shows callstack gets filled with calls and tail-recursion isn't supported although I use latest NodeJS. 这清楚地表明callstack充满了调用,虽然我使用最新的NodeJS,但不支持尾递归。

Does NodeJS/JavaScript support tail-recursion at all? NodeJS / JavaScript是否支持尾递归? Or do I really have to go with generators and yields, but problem here is my callbacks are gonna be heavily asynchronous and I'm not gonna work with return value anyway, I just need to make sure the callstack doesn't get uselessly filled with calls while function refers to itself in return. 或者我真的必须使用生成器和产量,但问题是我的回调将是非常异步的并且我无论如何都不会使用返回值,我只需要确保callstack不会无用地填充函数引用自身作为回报。

What you have there is not a tail-call. 你有什么不是尾巴调用。 A tail call is a function call performed as the final action of a another function. 尾调用是作为另一个函数的最终动作执行的函数调用。 A tail-recursive call is the same except the function calls itself. 除了函数调用自身之外,尾递归调用是相同的。

However, your code's final action is n*fac(n-1) , not fac(n-1) . 但是,您的代码的最终操作是n*fac(n-1) ,而不是fac(n-1) This is not a recursive tail call because the current stack still needs to remember n while computing the recursive calls so it will know which numbers to multiply. 这不是递归尾调用,因为当前堆栈在计算递归调用时仍需要记住n ,因此它将知道要乘以哪些数字。

What you can do is compute this information 1 step before: 您可以做的是在此之前计算此信息的步骤:

 const fac = (n, result = 1) => n === 1 ? result : fac(n - 1, n * result); console.log(fac(5)); // 120 

Or in terms of your code: 或者就您的代码而言:

function fac(n, result = 1) {
  // base case
  if (n === 1) {
    return result;
  }

  // compute the next result in the current stack frame
  const next = n * result;

  // propagate this result through tail-recursive calls
  return fac(n - 1, next);
}

Here's the stacktrace from Chrome Canary: 这是Chrome Canary的堆栈跟踪:

适用于Chrome金丝雀的正确电话

First off, if your actual case you're concerned about is when a function calls itself from an async callback, then you likely don't have any stack buildup there anyway. 首先,如果您关注的实际情况是函数从异步回调调用自身,那么您可能无论如何都没有任何堆栈堆积。

That's because the original function has already returned and the whole stack unwound before the async callback gets called, so though it visually looks like recursion, there is no stack build up. 这是因为原始函数已经返回并且整个堆栈在异步回调被调用之前解开,因此虽然它在视觉上看起来像递归,但是没有堆栈构建。

Here's a simple code example of making multiple sequenced network requests where a function calls itself from an async callback and there is no stack buildup. 这是一个简单的代码示例,用于生成多个有序网络请求,其中函数从异步回调调用自身,并且没有堆栈累积。

function getPages(baseURL, startParam, endParam, progressCallback) {

    function run(param) {
         request(baseURL + param, function(err, response, body) {
             if (err) return progressCallback(err);
             ++param;
             if (param < endParam) {
                 progressCallback(null, body);
                 // run another iteration
                 // there is no stack buildup with this call
                 run(param);
             } else {
                 // indicate completion of all calls
                 progressCallback(null, null);
             }
         });
    }
    run(startParam);
}

The prior invocation of run() has already returned and the stack has completely unwound before the async callback is called so while this visually looks like recursion, there is no stack buildup. 在调用异步回调之前, run()的先前调用已经返回并且堆栈已完全展开,因此在视觉上看起来像递归时,没有堆栈累积。


In the specific code you show, you can avoid the recursion entirely by rewriting using a while loop which would work efficiently in any version of Javascript: 在您显示的特定代码中,您可以完全通过使用while循环重写来避免递归,该循环可以在任何版本的Javascript中有效地工作:

 function fac(n){ var total = 1; while (n > 1) { total *= n--; } return total; } // run demo in snippet for (var i = 1; i <= 10; i++) { console.log(i, fac(i)) } 

I am not sure your recursive function has a tail call. 我不确定你的递归函数是否有尾调用。 May be you can try the following; 也许你可以试试以下;

 "use strict"; var fac = (n, r = 1) => n === 1 ? r : (r *= n, fac(n-1,r)); console.log(fac(5)); 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM