简体   繁体   English

Node.js 尾调用优化:可能与否?

[英]Node.js tail-call optimization: possible or not?

I like JavaScript so far, and decided to use Node.js as my engine partly because of this , which claims that Node.js offers TCO.我像JavaScript到目前为止,并决定使用Node.js的为我的发动机的部分原因是因为这个,它声称的Node.js提供TCO。 However, when I try to run this (obviously tail-calling) code with Node.js, it causes a stack overflow:但是,当我尝试使用 Node.js 运行此(显然是尾调用)代码时,会导致堆栈溢出:

function foo(x) {
    if (x == 1) {
        return 1;
    }
    else {
        return foo(x-1);
    }
}

foo(100000);

Now, I did some digging, and I found this .现在,我做了一些挖掘,我发现了这个 Here, it seems to say I should write it like this:这里,好像说我应该这样写:

function* foo(x) {
    if (x == 1) {
        return 1;
    }
    else {
        yield foo(x-1);
    }
}

foo(100000);

However, this gives me syntax errors.但是,这给了我语法错误。 I've tried various permutations of it, but in all cases, Node.js seems unhappy with something .我尝试了它的各种排列,但在所有情况下,Node.js 似乎对某些东西不满意。

Essentially, I'd like to know the following:基本上,我想知道以下内容:

  1. Does or doesn't Node.js do TCO? Node.js 是否有 TCO?
  2. How does this magical yield thing work in Node.js?这个神奇的yield东西在 Node.js 中是如何工作的?

There are two fairly-distinct questions here:这里有两个相当不同的问题:

  • Does or doesn't Node.js do TCO? Node.js 是否有 TCO?
  • How does this magical yield thing work in Node.js?这个神奇的 yield 东西在 Node.js 中是如何工作的?

Does or doesn't Node.js do TCO? Node.js 是否有 TCO?

TL;DR : Not anymore, as of Node 8.x . TL;DR不再是,从 Node 8.x 开始 It did for a while, behind one flag or another, but as of this writing (November 2017) it doesn't anymore because the underlying V8 JavaScript engine it uses doesn't support TCO anymore.它有一段时间,在一个或另一个标志背后,但在撰写本文时(2017 年 11 月),它不再支持,因为它使用的底层 V8 JavaScript 引擎不再支持 TCO。 See this answer for more on that.有关更多信息,请参阅此答案

Details:细节:

Tail-call optimization (TCO) is a required part of the ES2015 ("ES6") specification .尾调用优化 (TCO) 是ES2015(“ES6”)规范的必需部分 So supporting it isn't, directly, a NodeJS thing, it's something the V8 JavaScript engine that NodeJS uses needs to support.因此,直接支持它并不是 NodeJS 的事情,而是 NodeJS 使用的 V8 JavaScript 引擎需要支持的东西。

As of Node 8.x, V8 doesn't support TCO, not even behind a flag.从 Node 8.x 开始,V8 不支持 TCO,甚至不支持。 It may do (again) at some point in the future;它可能会(再次)在未来的某个时候这样做; see this answer for more on that.有关更多信息,请参阅此答案

Node 7.10 down to 6.5.0 at least (my notes say 6.2, but node.green disagrees) supported TCO behind a flag ( --harmony in 6.6.0 and up, --harmony_tailcalls earlier) in strict mode only.节点7.10下降到6.5.0至少(我的笔记说6.2,但node.green不同意)(TCO支持一个标志背后--harmony在6.6.0及以上, --harmony_tailcalls更早)仅在严格模式。

If you want to check your installation, here are the tests node.green uses (be sure to use the flag if you're using a relevant version):如果你想检查你的安装,这里是node.green使用的测试(如果你使用的是相关版本,请务必使用该标志):

function direct() {
    "use strict";
    return (function f(n){
      if (n <= 0) {
        return  "foo";
      }
      return f(n - 1);
    }(1e6)) === "foo";
}

function mutual() {
    "use strict";
    function f(n){
      if (n <= 0) {
        return  "foo";
      }
      return g(n - 1);
    }
    function g(n){
      if (n <= 0) {
        return  "bar";
      }
      return f(n - 1);
    }
    return f(1e6) === "foo" && f(1e6+1) === "bar";
}

console.log(direct());
console.log(mutual());
$ # Only certain versions of Node, notably not 8.x or (currently) 9.x; see above
$ node --harmony tco.js
true
true

How does this magical yield thing work in Node.js?这个神奇的yield东西在 Node.js 中是如何工作的?

This is another ES2015 thing ("generator functions"), so again it's something that V8 has to implement.这是 ES2015 的另一个东西(“生成器函数”),所以这也是 V8 必须实现的东西。 It's completely implemented in the version of V8 in Node 6.6.0 (and has been for several versions) and isn't behind any flags.它在 Node 6.6.0 的 V8 版本中完全实现(并且已经用于多个版本)并且没有任何标志。

Generator functions (ones written with function* and using yield ) work by being able to stop and return an iterator that captures their state and can be used to continue their state on a subsequent occasion.生成器函数(用function*编写并使用yield )通过能够停止并返回一个迭代器来工作,该迭代器捕获它们的状态,并可用于在随后的情况下继续它们的状态。 Alex Rauschmeyer has an in-depth article on them here .亚历克斯Rauschmeyer对他们进行了深入的文章在这里

Here's an example of using the iterator returned by the generator function explicitly, but you usually won't do that and we'll see why in a moment:这是一个显式使用生成器函数返回的迭代器的示例,但您通常不会这样做,我们稍后会看到原因:

"use strict";
function* counter(from, to) {
    let n = from;
    do {
        yield n;
    }
    while (++n < to);
}

let it = counter(0, 5);
for (let state = it.next(); !state.done; state = it.next()) {
    console.log(state.value);
}

That has this output:有这个输出:

0
1
2
3
4

Here's how that works:这是它的工作原理:

  • When we call counter ( let it = counter(0, 5); ), the initial internal state of the call to counter is initialized and we immediately get back an iterator;当我们调用counterlet it = counter(0, 5); )时,调用counter的初始内部状态被初始化,我们立即返回一个迭代器; none of the actual code in counter runs (yet). counter没有任何实际代码运行(还)。
  • Calling it.next() runs the code in counter up through the first yield statement.调用it.next()运行counter的代码,直到第一个yield语句。 At that point, counter pauses and stores its internal state.此时, counter暂停并存储其内部状态。 it.next() returns a state object with a done flag and a value . it.next()返回一个带有done标志和value的状态对象。 If the done flag is false , the value is the value yielded by the yield statement.如果done标志为false ,则value yield语句产生的值。
  • Each call to it.next() advances the state inside counter to the next yield .每次调用it.next()counter内的状态推进到下一个yield
  • When a call to it.next() makes counter finish and return, the state object we get back has done set to true and value set to the return value of counter .当一个呼叫it.next()使counter完成并返回,状态对象,我们找回了done设置为truevalue设置为返回值counter

Having variables for the iterator and the state object and making calls to it.next() and accessing the done and value properties is all boilerplate that (usually) gets in the way of what we're trying to do, so ES2015 provides the new for-of statement that tucks it all away for us and just gives us each value.拥有迭代器和状态对象的变量并调用it.next()并访问donevalue属性都是样板文件,(通常)会妨碍我们尝试做的事情,所以 ES2015 提供了新的for-of语句为我们隐藏了一切,只为我们提供了每个值。 Here's that same code above written with for-of :这是上面用for-of编写的相同代码:

"use strict";
function* counter(from, to) {
    let n = from;
    do {
        yield n;
    }
    while (++n < to);
}

for (let v of counter(0, 5)) {
    console.log(v);
}

v corresponds to state.value in our previous example, with for-of doing all the it.next() calls and done checks for us. v对应于我们之前示例中的state.valuefor-of为我们done所有it.next()调用和done检查。

node.js finally supports TCO since 2016.05.17, version 6.2.0 . node.js 终于从 2016.05.17版本 6.2.0 开始支持 TCO。

It needs to be executed with the --use-strict --harmony-tailcalls flags for TCO to work.它需要使用--use-strict --harmony-tailcalls标志来执行,TCO 才能工作。

6.2.0 - with 'use strict' and '--harmony_tailcalls' 6.2.0 - 使用“严格使用”和“--harmony_tailcalls”

works only with small tail-optimized recursions of 10000 (like in the question), but fails the function calls itself 99999999999999 times.仅适用于 10000 的小尾优化递归(如问题中所示),但函数调用自身 99999999999999 次失败。

7.2.0 with 'use strict' and '--harmony' 7.2.0 带有 'use strict' 和 '--harmony'

flag works seamlessly and rapidly even with 99999999999999 calls.即使使用 99999999999999 呼叫,flag 也能无缝快速地工作。

More concise answer... as of it's date of implementation, as mentioned...更简洁的答案......正如所提到的,截至实施日期......

TCO works. TCO 有效。 It's not bulletproof, but it is very decent.它不是防弹的,但它非常体面。 Here's Factorial(7000000,1).这是因子(7000000,1)。

>node --harmony-tailcalls -e "'use strict';function f(n,t){return n===1?t:f(n-1,n*t);}; console.log(f(7000000,1))"
Infinity

And here it is without TCO.它没有 TCO。

>node -e "'use strict';function f(n,t){return n===1?t:f(n-1,n*t);}; console.log(f(15669,1))"
[eval]:1
function f(n,t){return n===1?t:f(n-1,n*t);}; console.log(f(15669,1))
      ^

RangeError: Maximum call stack size exceeded
at f ([eval]:1:11)
at f ([eval]:1:32)
at f ([eval]:1:32)
at ...

It does make it all the way to 15668 though.尽管如此,它确实一直到15668。

As for yield, see other answers.至于产量,请参阅其他答案。 Should probably be a separated question.应该是一个单独的问题。

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

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