
JS 不像 Haskell,其自身從語言設計層面不支援惰性求值,但是可以通過文法去 模拟實作 這一特性;
想一想,我們可以用什麼來 JS 文法來模拟這一“延遲計算”的特性?
沒思路的話,看前篇這一句:
在《Haskell趣學指南》中,thunk 被翻譯成 保證;
在《Haskell 函數式程式設計入門》,thunk 被解釋為:
thunk 意為形實替換程式(有時候也稱為延遲計算,suspended computation)。它指的是在計算的過程中,一些函數的參數或者一些結果通過一段程式來代表,這被稱為 thunk。可以簡單地把 thunk 看做是一個未求得完全結果的表達式與求得該表達式結果所需要的環境變量組成的函數,這個表達式與環境變量形成了一個無參數的閉包(parameterless closure),是以 thunk 中有求得這個表達式所需要的所有資訊,隻是在不需要的時候不求而已。
那意思是用 Promise 模拟嗎?
事實上,不行!
Promise
一旦執行,它就開始執行了,你隻知道是在
Pending
,但不知道是剛開始執行,或者是快執行完了,還是其它哪個執行階段;擷取
Promise
的時候,内部的異步任務就已經啟動了,執行無法中途取消(這也是 Promise 的弊端之一:);
代碼????
Promise 是立即求值,不是惰性求值!
那手上還有什麼牌?
“延遲執行”不就是“暫停以後再執行”嘛?
thunk
更像是
Generator
!!????
指派的時候,我不進行計算,把你包裝成一個
<suspended>
暫停等待,等你調用
next()
的時候,我再計算;
代碼????
這不就是最簡單版本的 JS 惰性求值 Thunk 的實作嗎?
Haskell 中的無限清單不就是 MDN 中 Generator 所實作的 無限疊代器 嗎?
function* idMaker(){
let index = 0;
while(true)
yield index++;
}
let gen = idMaker(); // "Generator { }"
console.log(gen.next().value);
// 0
console.log(gen.next().value);
// 1
console.log(gen.next().value);
// 2
// ...
複制代碼
實際上 Lazy.js 也正是借助 Generator 實作“惰性”的!
以實作
take
方法為例????:
在 Haskell 中,
take
函數可以從頭連續地取得一個清單的幾個元素;
Prelude> take 3 [1,2,3,4,5]
[1,2,3]
複制代碼
JS 模拟實作
take
:
function* take(n,items){
let i = 0;
if (n < 1) return;
for (let item of items) {
yield item;
i++;
if (i >= n) {
return;
}
}
}
let thunk=take(3,[1,2,3,4,5])
console.log(thunk.next()) // {value: 1, done: false}
console.log(thunk.next()) // {value: 2, done: false}
console.log(thunk.next()) // {value: 3, done: false}
console.log(thunk.next()) // {value: undefined, done: true}
複制代碼
使用 lazy.js 是類似這樣調用的:
Lazy(stream)
.take(5) // 僅僅閱讀資料中的前五塊内容
.each(processData);
小結
專欄介紹引用的是這句話:
如果要整體了解一個人的核心 JavaScript 技能,我最感興趣的是他們會如何使用閉包以及如何充分利用異步。—— Jake Archibald
再回看 wiki 上關于閉包的這句解釋:
- 閉包的用途:
(暫且不論用于生成這個閉包對象本身的開銷,比如 C++ 中按值捕獲意味着執行複制構造函數),即“惰性求值”,是以它可以被用來定義控制結構。例如:在Smalltalk語言中,所有的控制結構,包括分支條件(if/then/else)和循環(while和for),都是通過閉包實作的。使用者也可以使用閉包定義自己的控制結構。因為閉包隻有在被調用時才執行操作
現在看來,惰性求值似乎能連接配接“如何使用閉包”和“如何充分利用異步”!!
“惰性”的思想深入函數式程式設計,還有最重要的 Monad,把具有“副作用”的部分延後處理,也與“惰性”呼應,後面有機會再讨論~