函数式JS: 本来promise是如许的monad

转载请说明出处: http://hai.li/2017/03/27/prom…

背景

上篇文章 函数式JS: 一种continuation monad推导 获得了一个相似promise的链式挪用,引发了如许的思索:岂非promise是monad?假如是的话又是怎样的monad呢?来来来,哥哥带你推倒,哦,不,是推导一下!

Monad

Monad是haskell里很主要的观点,作为一种范例,有着牢固的操作要领,简朴的能够类比面向对象的接口。

定义

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函数。

轨则

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范例的值,能够这么明白:

  1. 左单位元轨则就是将包裹unit(x)和函数f传给flatMap实行等价于将包裹中的值x抽出传给函数f实行

  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链式挪用整顿一下,将关注点集合在链式挪用上

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!

参考

    原文作者:defims
    原文地址: https://segmentfault.com/a/1190000008850740
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞