私はこれまでJavaScriptが好きで、Node.jsがTCOを提供していると主張する this の理由もあり、Node.jsをエンジンとして使用することにしました。ただし、この(明らかに末尾呼び出しの)コードをNode.jsで実行しようとすると、スタックオーバーフローが発生します。
function foo(x) {
if (x == 1) {
return 1;
}
else {
return foo(x-1);
}
}
foo(100000);
さて、掘り下げてみたところ、 this でした。ここでは、次のように書く必要があると言っているようです。
function* foo(x) {
if (x == 1) {
return 1;
}
else {
yield foo(x-1);
}
}
foo(100000);
ただし、これにより構文エラーが発生します。私はそれのさまざまな順列を試しましたが、すべての場合において、Node.jsは何かに不満を持っているようです。
基本的に、私は次のことを知りたいです:
yield
はNode.jsでどのように機能しますか?ここには2つのかなり明確な質問があります。
Node.jsはTCOを実行しますか?
TL; DR:Node 8.xしばらくの間、フラグの背後で実行されましたが、この記事の執筆時点(2017年11月)では、使用する基盤となるV8 JavaScriptエンジンがTCOをサポートしなくなったため、サポートされなくなりました。詳細については、 この回答 を参照してください。
詳細:
末尾呼び出しの最適化(TCO)は、ES2015( "ES6")仕様の必須 部分です 。したがって、それをサポートすることは、直接、NodeJSのものではなく、NodeJSが使用するV8JavaScriptエンジンがサポートする必要があるものです。
Node 8.xの時点で、V8はフラグの背後でもTCOをサポートしていません。将来のある時点で(再び)サポートする可能性があります。 これを参照してください。詳細については、answer を参照してください。
ノード7.10から少なくとも6.5.0まで(私のメモでは6.2と書かれていますが、 node.green は同意しません)、フラグの背後にあるTCOをサポートしていました(6.6.0以降では_--harmony
_、 _--harmony_tailcalls
_以前)ストリクトモードのみ。
インストールを確認したい場合は、 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());
_
$#特定のバージョンのNodeのみ、特に8.xまたは(現在)9.xではありません。上記を参照 $ node --harmony tco.js true true
この魔法の
yield
はNode.jsでどのように機能しますか?
これはES2015のもう1つの機能(「ジェネレーター関数」)であるため、V8で実装する必要があります。 Node 6.6.0のV8のバージョンで完全に実装されており(いくつかのバージョンで使用されています)、フラグの背後にはありません。
ジェネレーター関数(_function*
_で記述され、yield
を使用する関数)は、状態をキャプチャし、その後の状態を継続するために使用できるイテレーターを停止して返すことができるため、機能します。 Alex Rauschmeyerには、それらに関する詳細な記事があります here 。
ジェネレーター関数によって返されるイテレーターを明示的に使用する例を次に示しますが、通常はそうしません。その理由はすぐにわかります。
_"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);
}
_
これには次の出力があります。
0 1 2 [。
仕組みは次のとおりです。
counter
(let it = counter(0, 5);
)を呼び出すと、counter
の呼び出しの初期内部状態が初期化され、すぐにイテレータが返されます。 counter
の実際のコードは(まだ)実行されません。it.next()
を呼び出すと、counter
のコードが最初のyield
ステートメントまで実行されます。その時点で、counter
は一時停止して内部状態を保存します。 it.next()
は、done
フラグとvalue
を持つ状態オブジェクトを返します。 done
フラグがfalse
の場合、value
はyield
ステートメントによって生成される値です。it.next()
を呼び出すたびに、counter
内の状態が次のyield
に進みます。it.next()
の呼び出しでcounter
が終了して戻ると、返される状態オブジェクトのdone
はtrue
とvalue
に設定されます。 counter
の戻り値に設定します。イテレータと状態オブジェクトの変数を用意し、it.next()
を呼び出し、done
プロパティとvalue
プロパティにアクセスすることは、(通常)何を妨げるかという定型的なものです。私たちがやろうとしているので、ES2015は新しい_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
は前の例の_state.value
_に対応し、_for-of
_はすべてのit.next()
呼び出しとdone
チェックを実行します。
node.jsは、2016.05.17以降、ついにTCOをサポートします バージョン6.2. 。
TCOが機能するには、--use-strict --harmony-tailcalls
フラグを指定して実行する必要があります。
6.2.0-「usestrict」および「--harmony_tailcalls」を使用
(質問のように)10000の小さなテール最適化再帰でのみ機能しますが、関数はそれ自体を999999999999999回呼び出すことに失敗します。
7.2.0「usestrict」および「--harmony」
フラグは、99999999999999の呼び出しでも、シームレスかつ迅速に機能します。