NodeJS中的尾递归

时间:2017-01-15 21:54:30

标签: javascript node.js recursion tail-call-optimization

所以我最近遇到过这样的情况:我需要编写回调调用自己的代码等等,并且想知道NodeJS和尾调用支持,所以我发现这个答案是https://stackoverflow.com/a/30369729,说是的,它是' s支撑。

所以我用这个简单的代码尝试了它:

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

fac(5);

在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)

虽然我使用了最新的NodeJS,但它清楚地表明callstack充满了调用并且不支持尾递归。

NodeJS / JavaScript是否支持尾递归? 或者我真的必须使用发电机和产量,但问题是我的回调会非常异步并且我无论如何都不会使用返回值,我只需要确保callstack不会无用地填充调用,而函数引用自身作为回报。

3 个答案:

答案 0 :(得分:6)

你所拥有的不是尾巴。尾调用是作为另一个函数的最终操作执行的函数调用。除了函数调用自身之外,尾递归调用是相同的。

但是,您的代码的最终操作是n*fac(n-1),而不是fac(n-1)。这不是递归尾调用,因为当前堆栈在计算递归调用时仍需要记住n,因此它将知道要乘以哪些数字。

您可以做的是在此之前计算此信息:

const fac = (n, result = 1) =>
  n === 1
    ? result
    : fac(n - 1, n * result);

console.log(fac(5)); // 120

或者就代码而言:

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);
}

以下是Chrome Canary的堆栈跟踪:

Proper Tail Calls in Chrome Canary

答案 1 :(得分:3)

首先,如果您关心的实际情况是函数从异步回调中调用自身,那么您可能无论如何都没有堆栈堆积。

那是因为原始函数已经返回并且整个堆栈在异步回调被调用之前解开,所以虽然它在视觉上看起来像递归,但是没有堆栈构建。

这是一个简单的代码示例,用于创建多个有序网络请求,其中函数从异步回调调用自身,并且没有堆栈累积。

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);
}

在调用异步回调之前,run()的先前调用已经返回并且堆栈已完全展开,因此在视觉上看起来像递归时,没有堆栈堆积。

在您显示的特定代码中,您可以完全通过使用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))
}

答案 2 :(得分:0)

我不确定你的递归函数是否有尾调用。也许你可以试试以下内容;

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