2017-01-15 13 views
4

私は最近、コールバックが自分自身をコールするコードを書く必要があり、NodeJSとテールコールサポートについて疑問を抱いていたので、私はこの答えを見つけましたhttps://stackoverflow.com/a/30369729それはサポートされていると言っています。NodeJSのテール再帰

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

fac(5); 

のLinux x64の上でノード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はテール再帰をまったくサポートしていますか? または、実際にジェネレータとyieldで行く必要がありますが、ここでの問題は、コールバックが非常に非同期になり、返り値で動作しないためです。コールスタックが無駄にならないようにする必要があります呼び出しは関数がそれ自体を参照する間に呼び出されます。

+0

あなたは可能性があり再帰の代わりに 'while'ループを使って書き直してください。 – jfriend00

+0

私が問題にしているように、コールバックは大量の非同期IO依存であるため、戻り値は気にしません。したがって、whileを使用することはできません。 – jakubinf

+0

実際の例を表示することをお勧めします。非同期コールバックが呼び出される前に、元の関数が既に返されているため、非同期コールバックから実際にスタックビルドアップにつながることはありません。あなたが本当の問題を示していれば、あなたの本当の問題をより良く助けることができます。 – jfriend00

答えて

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()の前の呼び出しは、すでに戻ってきたとスタックが、これは視覚的に再帰のように見えながら、何のスタックの蓄積がないので、非同期コールバックが呼び出される前に完全に解かれています。

あなたが表示され、特定のコードで

、あなたはJavascriptの任意のバージョンで効率的に働くだろうwhileループを使用して書き換えることにより、完全に再帰を避けることができます。

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

+0

コールバック関数が非同期IO依存である場合、時にはこれは不可能です。 – jakubinf

+0

@jakubinf - あなたが話している正確なコードに依存します。同じ関数を非同期コールバックから繰り返し呼び出すとスタックが増えることはありません。したがって、そこで解決する問題はありません。再帰呼び出しのように見えますが、スタックはすでに元に戻されています。元の関数は関数が再度呼び出される前に返されます。スタックの積み重ねの点では再帰関数ではありません。 – jfriend00

+0

@jakubinf - 私は "非同期再帰"のケースについて私の質問に多くの情報を追加しました。 – jfriend00

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

またはあなたのコードの面で:あなたは何ができるか

は前にこの情報を1つのステップの計算である

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

ここクロームカナリアからのスタックトレースです:

Proper Tail Calls in Chrome Canary

+0

まあ、これはかなり面白いですが、console.traceは渡された現在の実行によってスタックフレームを出力できます。あなたが提供したコードを使用してfac(1000)を実行すると、1000行が出力されます。 – jakubinf

+0

それはRAMの中に無駄なスペースをとってどこかに保存しなければならないことを意味します。もしfac(1000000000)を作れば、おそらく少なくとも4GBのRAMが使用されてしまいます。これは非常に効率が悪く、間違っていれば修正します。 – jakubinf

+0

最後の10ステップを覚えているだけのスタックトレースですか? – jakubinf

関連する問題