NodeJS 中的尾递归

Tail recursion in NodeJS

所以我最近遇到了一个案例,我需要编写回调调用自身的代码等等,并且想知道 NodeJS 和尾调用支持,所以我找到了这个答案 说是的,它受支持。

所以我用这个简单的代码试了一下:

"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,但它清楚地表明调用堆栈充满了调用并且不支持尾递归。

NodeJS/JavaScript 完全支持尾递归吗? 还是我真的必须使用生成器和收益,但这里的问题是我的回调将是高度异步的,无论如何我都不会使用 return 值,我只需要确保调用堆栈不会当函数在 return.

中引用自身时,会无用地充满调用

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

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

你那里没有 tail-call。尾调用是作为另一个函数的最终操作执行的函数调用。 tail-recursive 调用是相同的,除了函数调用自身。

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

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

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 的堆栈跟踪:

首先,如果您关心的实际情况是某个函数从异步回调中调用自身,那么您可能在那里根本没有任何堆栈堆积。

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

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

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