函數式JS: 原來promise是這樣的monad

轉載請註明出處: http://hai.li/2017/03/27/prom...javascript

背景

上篇文章 函數式JS: 一種continuation monad推導 獲得了一個相似promise的鏈式調用,引起了這樣的思考:難道promise是monad?若是是的話又是怎樣的monad呢?來來來,哥哥帶你推倒,哦,不,是推導一下!html

Monad

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是通常的值,fg是通常的函數,monad是一個Monad類型的值,能夠這麼理解:git

  1. 左單位元法則就是將包裹unit(x)和函數f傳給flatMap執行等價於將包裹中的值x抽出傳給函數f執行github

  2. 右單位元法則就是將包裹monad和函數unit傳給flatMap執行等價於包裹monad自己(有點像1*1=1編程

  3. 關聯性法則就是將包裹monad和函數f傳給flatMap執行,再將執行的結果和函數g傳給flatMap執行等價於將包裹monad中的值x抽出傳給f執行(執行結果依然是Monad類型),再將執行結果中的值x抽出傳給g執行數組

Promise

鏈式調用

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

unitflatMap的特性能夠直觀地對應爲閉包

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)

這樣若是unitflatMap這兩個直接函數能夠構造推導出來,就能夠窺探Promise的真面目了。同窗們!這道題!必考題!頭兩年不考,今年確定考!

構造推導unit

function unit(f){ return f}
  1. flatMap :: Monad a -> (a -> Monad b) -> Monad bflatMap(unit(g0))(g1)可知傳入g1的參數就是a,對應着"0"

  2. 但由unit :: a -> Monad aunit(g0)獲得的a卻對應着g0。實際上a對應着"0",只是ag0裏做爲當即量傳入,在g1g2的返回值中做爲閉包引用傳入。

  3. 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
        })
    }
}
  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 }}
  2. 實際flatMap作了3步工做

    1. 解包ma取出a

    2. a傳到g1中執行

    3. 將執行結果b包裹成mb返回

  3. 這裏mag1都是容器,經過回調獲得輸出結果,因此在ma的回調中執行g1(a),再在g1(a)的回調中獲得執行結果v,再將執行結果v賦值給外部變量b,最後將bunit包裹成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)})
        }
    }
  4. 若是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 })
        }
    }
  5. 爲了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) })
        }
    }
  6. 整合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
        })
        }
    }
  7. 因爲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
            })
        }
    }
  8. 如今能夠測試下代碼了

    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)

Promise是Monad嗎?

將整合代碼中unit改爲newPromiseflatMap改爲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
  1. newPromise的輸入是一個函數,但在推導構造unit裏解釋過,這裏藉助了當即量和閉包引用來額外增長了輸入anewPromise的參數則做爲構造unit的補充邏輯。

  2. newPromise的輸出是a的包裹Monad a

  3. newPromise的方法then藉助了閉包引用額外輸入了Monad a,而輸入的g函數輸入是a輸出則是藉助newPromise實現的Monad b

  4. 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) }) //關聯性
  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 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
  2. 右單位元法則驗證代碼以下:

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

如此,原來Promise是這樣的Monad!

參考

相關文章
相關標籤/搜索