• 函数式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!

    参考

  • 相关阅读:
    dva实用的学习笔记
    上传图片到七牛云
    Lodash学习笔记
    Ant Design Pro 脚手架+umiJS 实践总结
    SVN的安装和使用手册
    判断数据类型的5种方法
    常见react面试题汇总(适合中级前端)
    Es6 类class的关键 super、static、constructor、new.target
    ES2019 新特性简介
    通用正则实战200
  • 原文地址:https://www.cnblogs.com/feng9exe/p/8630389.html
Copyright © 2020-2023  润新知