2014-04-24 57 views
17

我目前喜歡JavaScript,並決定使用Node.js作爲我的引擎,部分原因是this,它聲稱Node.js提供TCO。然而,當我嘗試運行與此的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似乎都不滿意,這是

從本質上講,我想了解以下內容:

  1. 或不Node.js的做TCO?
  2. 這個神奇的yield在Node.js中是如何工作的?
+1

用'--harmony'標誌運行節點來查看你的第二個版本是如何工作的。例如'node --harmony mytest.js'。但首先重新審視你引用的例子,你只是將它的一部分適用於你的案例。關於TCO,真正的問題是V8是否已經實現了它 - 並且在[v8更新日誌]中沒有提及已經完成的工作(https://code.google.com/p/v8/source/browse/trunk/ChangeLog ),我可以看到。 –

+0

@ barry-johnson:我嘗試在第二個鏈接中使用「yield」複製樣例函數,而Node.js對「function *」異常。這是我困惑的原因之一。 –

+0

這就是爲什麼我說你需要使用--harmony選項運行節點。生成器是ES6/Harmony的一部分,它不是節點的默認設置。 –

回答

30

這裏有兩個相當,不同的問題:

  • 或不Node.js的做TCO?
  • 在Node.js中,這個神奇的產量是如何工作的?

或不Node.js的做TCO?

TL; DR不一樣了,因爲節點8.x的的。它已經有一段時間,落後於一面旗幟或另一面旗幟,但截至撰寫本文時(2017年11月),它已經不復存在了,因爲它使用的底層V8 JavaScript引擎不再支持TCO。有關更多信息,請參閱this answer

詳情:

尾部調用優化(TCO)是必需的part of the ES2015 ("ES6") specification。因此,直接支持它並不是NodeJS,它是NodeJS使用的V8 JavaScript引擎需要支持的東西。

從節點8.x開始,V8不支持TCO,甚至不支持標誌。它可能會在未來的某個時候再次發生;有關更多信息,請參閱this answer

節點7.10下降到6.5.0至少(我的筆記說6.2,但不同意node.green)只在嚴格的模式下支持TCO標誌後面(--harmony在6.6.0及以上,--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()); 
 
$ # Only certain versions of Node, notably not 8.x or (currently) 9.x; see above 
$ node --harmony tco.js 
true 
true 

如何這個神奇的yield事情在Node.js中工作?

這是另一個ES2015的東西(「發電機功能」),所以它也是V8必須實現的東西。它完全在Node 6.6.0的V8版本中實現(並且已經有多個版本),並且沒有落後於任何標誌。

生成器函數(用function*yield編寫的函數)能夠停止並返回捕獲其狀態並可用於在後續場合繼續其狀態的迭代器。亞歷克斯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 
3 
4 

這裏是如何工作的:

  • 當我們調用counterlet 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設置爲truevalue設置爲counter返回值。

具有迭代器和狀態對象變量和撥打電話到it.next()和訪問donevalue性能是所有樣板是(通常)獲取的是我們正在努力做的方式,因此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檢查我們。

+0

請儘可能在同一行上 - 看起來更清晰 - 無法編輯 - 因爲更改2個字符不足以進行編輯... – AndyS

+0

@AndyS:純風格編輯不適合任何情況。 –

4

node.js自2016.05.17,version 6.2.0終於支持TCO。

需要使用--use-strict --harmony-tailcalls標誌執行TCO才能正常工作。

+1

鏈接頁面上沒有任何「尾巴」或「TCO」,你能鏈接到宣佈尾聲呼叫支持的東西嗎? (支持*是*,我已經檢查過了。)還要注意'--use-strict'不需要啓用它,只需要'--harmony-tailcalls'。 –

+1

另請注意,*理由*它背後的旗幟是V8團隊不認爲它穩定,更不用說了。它們仍然(如Node v6.2.2中的V8)認爲它正在進行*。 –

+0

@ T.J.Crowder:雖然沒有被認爲是穩定的,但到目前爲止它對我很好。 – yairchu

-1

更簡潔的答案... ...爲它的實現之日起,如提及...

TCO工作。這不是防彈的,但它非常體面。這裏是因子(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 

而且這裏沒有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 ... 

它確實使它成爲15668儘管如此。

至於產量,請參閱其他答案。應該可能是一個單獨的問題。

+0

的升級。爲了讓CPU燃燒,將它撞上了幾個數量級。 – TylerY86

0

6.2.0 - 與「使用嚴格」和「--harmony_tailcalls」

作品只有10000小尾巴優化遞歸(如你問題),但失敗的函數調用自身99999999999999次。

7.2.0與 '使用嚴格' 和 '--harmony'

標誌無縫工作,並迅速甚至99999999999999個電話。

+0

99999999999999?這是很多電話。它是否以某種方式優化了計算? – yairchu