轉載請註明出處: http://hai.li/2017/03/27/prom...javascript
上篇文章 函數式JS: 一種continuation monad推導 獲得了一個相似promise的鏈式調用,引起了這樣的思考:難道promise是monad?若是是的話又是怎樣的monad呢?來來來,哥哥帶你推倒,哦,不,是推導一下!html
Monad是haskell裏很重要的概念,做爲一種類型,有着固定的操做方法,簡單的能夠類比面向對象的接口。java
unit :: a -> Monad a flatMap :: Monad a -> (a -> Monad b) -> Monad b
這是類型簽名的表述。unit
的做用能夠理解爲將a
放入容器中變成Monad a
。而當flatMap
轉爲(a -> Monad b) -> (Monad a -> Monad b)
時,它的做用就能夠理解爲將a -> Monad b
函數轉換成Monad a -> Monad b
函數。ios
flatMap(unit(x), f) ==== f(x) //左單位元 flatMap(monad, unit) ==== monad //右單位元 flatMap(flatMap(monad, f), g) ==== flatMap(monad, function(x) { flatMap(f(x), g) }) //關聯性
這裏x
是通常的值,f
和g
是通常的函數,monad
是一個Monad
類型的值,能夠這麼理解:git
左單位元法則就是將包裹unit(x)
和函數f
傳給flatMap
執行等價於將包裹中的值x
抽出傳給函數f
執行github
右單位元法則就是將包裹monad
和函數unit
傳給flatMap
執行等價於包裹monad
自己(有點像1*1=1
)編程
關聯性法則就是將包裹monad
和函數f
傳給flatMap
執行,再將執行的結果和函數g
傳給flatMap
執行等價於將包裹monad
中的值x
抽出傳給f
執行(執行結果依然是Monad
類型),再將執行結果中的值x
抽出傳給g
執行數組
new Promise(function(resolve) { setTimeout(function() { resolve("0") }, 100) }) .then(function(v) { console.log(v); return new Promise(function(resolve) { resolve(v + "->1") }) }) .then(function(v) { console.log(v); return new Promise(function(resolve) { setTimeout(function() { resolve(v + "->2") }, 1000) }) }) .then(console.log)
先將Promise
鏈式調用整理一下,將關注點集中在鏈式調用上promise
function f0(resolve) { setTimeout(function() { resolve("0") }, 100) } function f1(v) { console.log(v); return new Promise(function(resolve) { resolve(v + "->1") }) } function f2(v) { console.log(v); return new Promise(function(resolve) { setTimeout(function() { resolve(v + "->2") }, 1000) }) } function f3(v) { console.log(v) } new Promise(f0).then(f1).then(f2).then(f3) //0 0->1 0->1->2
從unit
和flatMap
的特性能夠直觀地對應爲閉包
function g0(resolve) { setTimeout(function() { resolve("0") }, 100) } function g1(v) { console.log(v); return unit(function(resolve) { resolve(v + "->1") }) } function g2(v) { console.log(v); return unit(function(resolve) { setTimeout(function() { resolve(v + "->2") }, 1000) }) } function g3(v) { console.log(v) } unit(g0).flatMap(g1).flatMap(g2).flatMap(f3)
而對象的方法能夠經過將this
做爲參數傳入方便地轉爲直接的函數,好比
var a = {method: function f(v){ console.log(this, v) }} var a_method = function(t, v){ console.log(t, v) } a.method("a") === a_method(a, "a")
這樣將鏈式調用轉爲嵌套函數調用變成
function g0(resolve) { setTimeout(function() { resolve("0") }, 100) } function g1(v) { console.log(v); return unit(function(resolve) { resolve(v + "->1") }) } function g2(v) { console.log(v); return unit(function(resolve) { setTimeout(function() { resolve(v + "->2") }, 1000) }) } function g3(v) { console.log(v) } flatMap( flatMap( flatMap( unit(g0) )(g1) )(g2) )(g3)
這樣若是unit
和flatMap
這兩個直接函數能夠構造推導出來,就能夠窺探Promise
的真面目了。同窗們!這道題!必考題!頭兩年不考,今年確定考!
unit
function unit(f){ return f}
由flatMap :: Monad a -> (a -> Monad b) -> Monad b
和flatMap(unit(g0))(g1)
可知傳入g1
的參數就是a
,對應着"0"
。
但由unit :: a -> Monad a
和unit(g0)
獲得的a
卻對應着g0
。實際上a
對應着"0"
,只是a
在g0
裏做爲當即量傳入,在g1
和g2
的返回值中做爲閉包引用傳入。
Monad
可看做容器,那用什麼作的容器呢?既然做爲參數傳入unit
的函數f
已經包裹了a
,那試試直接做爲Monad a
返回。同時根據g0
看出返回值f
是用回調返回值的。也就是將一個用回調返回結果的函數做爲容器。
flatMap
function flatMap(ma){ return function(g) { var b=[], ga, h=[]; ma(function(a) { //1. 解包`ma`取出`a` ga = g(a); //2. 將`a`傳到`g`中執行 (ga && ga.flatMap ? ga : unit(function(c) { c(ga) })) //處理g沒返回unit狀況 (function(v) { b.push(v); // 1.1—1.2—1.3 h.map(function(c) {c(v)}) //1.1—1.3—1.2 }) }); return unit(function(c) { //3. 將執行結果`b`包裹成`mb`返回 b.length ? b.map(c) // 1.1—1.2—1.3—2.1 : h.push(c) //1.1—1.3—1.2—2.1 }) } }
由flatMap :: Monad a -> (a -> Monad b) -> Monad b
知道flatMap
傳入Monad a
返回函數,這個函數接收(a -> Monad b)
返回Monad b
,而(a -> Monad b)
對應g1
。能夠構造flatMap
以下
function flatMap(ma){return function(g1) { /*b = g1(a);*/ return mb }}
實際flatMap
作了3步工做
解包ma
取出a
將a
傳到g1
中執行
將執行結果b
包裹成mb
返回
這裏ma
和g1
都是容器,經過回調獲得輸出結果,因此在ma
的回調中執行g1(a)
,再在g1(a)
的回調中獲得執行結果v
,再將執行結果v
賦值給外部變量b
,最後將b
用unit
包裹成Monad b
返回。
function flatMap(ma){ return function(g1) { var b; ma(function(a) { g1(a)(function(v) { b = v }) }); return unit(function(c) {c(b)}) } }
若是g1
是當即執行的話,第flatMap
的執行步驟是1--2--3,但若是2延遲執行步驟就變成了1--3--2,算上下一個flatMap
就是1.1--1.3--1.2--2.1。2.1的ma
就是1.2的mb
,2.1的ma
的參數c
中執行了2.2和2.3,也就是1.3的c
決定着2.1以後的步驟。若是將c
賦值給b
就能夠在1.2執行完後才繼續2.1以後的步驟,也就是:
+--------------+ 1.1—1.2—1.3—2.1 2.2—2.3
function flatMap(ma){ return function(g1) { var b; ma(function(a) { g1(a)(function(v) { b(v) }) }); return unit(function(c) { b = c }) } }
爲了flatMap
能夠連接多個flatMap
,也就是一個1.3被多個2.1消化,須要保存全部在2.1後的執行鏈 c
,用數組h
解決。
function flatMap(ma){ return function(g1) { var h=[]; ma(function(a) { g1(a)(function(v) { h.map(function(c) {c(v)}) }) }); return unit(function(c) { h.push(c) }) } }
整合1.2當即執行和延遲執行狀況,同時適配多個1.3被多個2.1消化的狀況,代碼以下:
function flatMap(ma){ return function(g1) { var b=[], h=[]; ma(function(a) { g1(a)(function(v) { b.push(v); // 1.1—1.2—1.3 h.map(function(c) {c(v)}) //1.1—1.3—1.2 }) }); return unit(function(c) { b.length ? b.map(c) // 1.1—1.2—1.3—2.1 : h.push(c) //1.1—1.3—1.2—2.1 }) } }
因爲g3
沒有返回mb
,因此還要加上對g1
返回的不是容器的處理,代碼以下:
function flatMap(ma){ return function(g1) { var b=[], g1a, h=[]; ma(function(a) { g1a = g1(a); (g1a && typeof(g1a) == "function" ? g1a : unit(function(c) { c(g1a) })) (function(v) { b.push(v); // 1.1—1.2—1.3 h.map(function(c) {c(v)}) //1.1—1.3—1.2 }) }); return unit(function(c) { b.length ? b.map(c) // 1.1—1.2—1.3—2.1 : h.push(c) //1.1—1.3—1.2—2.1 }) } }
如今能夠測試下代碼了
function unit(f){ return f } function flatMap(ma) { return function(g) { var b=[], ga, h=[]; ma(function(a) { //1. 解包`ma`取出`a` ga = g(a); //2. 將`a`傳到`g`中執行 (ga && typeof(ga) == "function"? ga : unit(function(c) { c(ga) })) //處理g沒返回unit狀況 (function(v) { b.push(v); // 1.1—1.2—1.3 h.map(function(c) {c(v)}) //1.1—1.3—1.2 }) }); return unit(function(c) { //3. 將執行結果`b`包裹成`mb`返回 b.length ? b.map(c) // 1.1—1.2—1.3—2.1 : h.push(c) //1.1—1.3—1.2—2.1 }) } } function g0(resolve) { setTimeout(function() { resolve("0") }, 100) } function g1(v) { console.log(v); return unit(function(resolve) { resolve(v + "->1") }) } function g2(v) { console.log(v); return unit(function(resolve) { setTimeout(function() { resolve(v + "->2") }, 1000) }) } function g3(v) { console.log(v) } flatMap( flatMap( flatMap( unit(g0) )(g1) )(g2) )(g3)
如今將嵌套函數變回鏈式調用,這裏也能夠用是否有flatMap
方法來判斷g1
是否返回容器
function unit(ma) { ma.flatMap = function(g){ var b=[], ga, h=[]; ma(function(a) { //1. 解包`ma`取出`a` ga = g(a); //2. 將`a`傳到`g`中執行 (ga && ga.flatMap ? ga : unit(function(c) { c(ga) })) //處理g沒返回unit狀況 (function(v) { b.push(v); // 1.1—1.2—1.3 h.map(function(c) {c(v)}) //1.1—1.3—1.2 }) }); return unit(function(c) { //3. 將執行結果`b`包裹成`mb`返回 b.length ? b.map(c) // 1.1—1.2—1.3—2.1 : h.push(c) //1.1—1.3—1.2—2.1 }) } return ma } function g0(resolve) { setTimeout(function() { resolve("0") }, 100) } function g1(v) { console.log(v); return unit(function(resolve) { resolve(v + "->1") }) } function g2(v) { console.log(v); return unit(function(resolve) { setTimeout(function() { resolve(v + "->2") }, 1000) }) } function g3(v) { console.log(v) } unit(g0).flatMap(g1).flatMap(g2).flatMap(g3)
將整合代碼中unit
改爲newPromise
,flatMap
改爲then
,哇塞,除了new Promise
中的空格請問哪裏有差?雖然改爲構造函數使得newPromise
改爲new Promise
也是分分鐘的事情,但重點不是這個,重點是Promise是Monad嗎?粗看是!
function newPromise(ma) { ma.then = function(g){ var b=[], ga, h=[]; ma(function(a) { ga = g(a); (ga && ga.then ? ga : newPromise(function(c) { c(ga) })) (function(v) { b.push(v); h.map(function(c) {c(v)}) }) }); return newPromise(function(c) { b.length ? b.map(c) : h.push(c) }) } return ma } newPromise(function(resolve) { setTimeout(function() { resolve("0") }, 100) }) .then(function(v) { console.log(v); return newPromise(function(resolve) { resolve(v + "->1") }) }) .then(function(v) { console.log(v); return newPromise(function(resolve) { setTimeout(function() { resolve(v + "->2") }, 1000) }) }) .then(console.log)
unit :: a -> Monad a flatMap :: Monad a -> (a -> Monad b) -> Monad b
將定義改下名
newPromise :: a -> Monad a then :: Monad a -> (a -> Monad b) -> Monad b
newPromise
的輸入是一個函數,但在推導構造unit
裏解釋過,這裏藉助了當即量和閉包引用來額外增長了輸入a
,newPromise
的參數則做爲構造unit
的補充邏輯。
newPromise
的輸出是a
的包裹Monad a
。
newPromise
的方法then
藉助了閉包引用額外輸入了Monad a
,而輸入的g
函數輸入是a
輸出則是藉助newPromise
實現的Monad b
。
newPromise
的方法then
輸出的是藉助newPromise
實現的Monad b
,這裏和g
的輸出Monad b
不是同一個Monad
可是b
確實相同的。
flatMap(unit(x), f) === f(x) //左單位元 flatMap(monad, unit) === monad //右單位元 flatMap(flatMap(monad, f), g) === flatMap(monad, function(x) { flatMap(f(x), g) }) //關聯性
將法則改下名,同時改成鏈式調用
newPromise(x).then(f) ==== f(x) //左單位元 monad.then(newPromise) ==== monad //右單位元 monad.then(f).then(g) ==== monad.then(function(x) { f(x).then(g) }) //關聯性
左單位元法則驗證代碼以下:
function newPromise(ma) { ma.then = function(g){ var b=[], ga, h=[]; ma(function(a) { ga = g(a); (ga && ga.then ? ga : newPromise(function(c) { c(ga) })) (function(v) { b.push(v); h.map(function(c) {c(v)}) }) }); return newPromise(function(c) { b.length ? b.map(c) : h.push(c) }) } return ma } var x = 1; var f = function(v){ return v + 2 } newPromise(function(resolve) { resolve(x) }).then(f).then(console.log) //3 console.log(f(x)) //3
右單位元法則驗證代碼以下:
function newPromise(ma) { ma.then = function(g){ var b=[], ga, h=[]; ma(function(a) { ga = g(a); (ga && ga.then ? ga : newPromise(function(c) { c(ga) })) (function(v) { b.push(v); h.map(function(c) {c(v)}) }) }); return newPromise(function(c) { b.length ? b.map(c) : h.push(c) }) } return ma } newPromise(function(resolve) { resolve(1) }).then(newPromise).then(console.log) //1 newPromise(function(resolve) { resolve(1) }).then(console.log) //1
關聯性法則驗證代碼以下:
function newPromise(ma) { ma.then = function(g){ var b=[], ga, h=[]; ma(function(a) { ga = g(a); (ga && ga.then ? ga : newPromise(function(c) { c(ga) })) (function(v) { b.push(v); h.map(function(c) {c(v)}) }) }); return newPromise(function(c) { b.length ? b.map(c) : h.push(c) }) } return ma } var f = function(v) { return newPromise(function(resolve) { resolve(v+2) }) } var g = function(v) { return newPromise(function(resolve) { resolve(v+3) }) } newPromise(function(resolve) { resolve(1) }).then(f).then(g).then(console.log) //6 newPromise(function(resolve) { resolve(1) }).then(function(x) { return f(x).then(g) }).then(console.log) //6