你造 Promise 就是 Monad 吗

Monad 这个概念好难解释, 你能够理解为一个 Lazy 或者是状态未知的盒子. 听起来像是薛定谔猫(估计点进去你会更晕了). 其实就是的, 在你打开这个盒子以前, 你是不知道里面的猫处在那种状态.javascript

Monad 这个黑盒子, 里面到底卖的神马药,咱们要打开喝了才知道.html

等等, 不是说好要解释 Either 的吗, 嗯嗯, 这里就是在解释 Either. 上节说 Either 是一个 Functor, 能够被 fmap over. 怎么这里又说道黑盒子了? 好吧, Monad 其实也是 Functor. 还记得我说的 Functor 实际上是一个带 context 的盒子吗. 而 fmap 使得往盒子里应用函数变换成为了可能.java

Either

先来看看 Either 这种类型会干什么事情. Either 表示要不是左边就是右边的值, 所以咱们能够用它来表示薛定谔猫, 要不是活着, 要不死了. Either 还有个方法:
eithergit

(a -> c) -> (b -> c) -> Either a b -> c

想必你已经对箭头->很是熟了吧.若是前面几章你都跳过了,我再翻译下好了. 这里表示接收函数a->c和函数 b->c, 再接收一个 Either, 若是 Either 的值在左边,则使用函数映射 a->c, 若值在右边,则应用第二个函数映射 b->c.github

做为 Monad, 它还必须具有一个方法 '>>='(这个符号好眼熟的说, 看看 haskell 的 logo, 你就知道 Monad 是有多重要), 也就是 bind 方法.ajax

bind 方法的意思很简单, 就是给这个盒子加一个操做, 好比往盒子在加放射性原子,若是猫活着,就是绿巨猫, 若是猫是死的,那就是绿巨死猫.json

Left("cat").bind(cat => 'hulk'+cat)
// => Left "hulkcat"
Right("deadcat").bind(cat => 'hulk' + cat)
// => Right "hulkdeadcat"

这有个毛用啊. 表急... 来看个经典例子api

走钢索

皮尔斯决定要辞掉他的工做改行试着走钢索。他对走钢索蛮在行的,不过仍有个小问题。就是鸟会停在他拿的平衡竿上。他们会飞过来停一小会儿,而后再飞走。这样的状况在两边的鸟的数量同样时并非个太大的问题。但有时候,全部的鸟都会想要停在同一边,皮尔斯就失去了平衡,就会让他从钢索上掉下去。promise

咱们这边假设两边的鸟差别在三个以内的时候,皮尔斯仍能保持平衡。app

通常解法

首先看看不用 Monad 怎么解

eweda.installTo(this);
var landLeft = eweda.curry(function(n, pole){
    return [pole[0]+n, pole[1]];
});
var landRight = eweda.curry(function(n, pole){
    return landLeft(n, eweda.reverse(pole));
});
var result = eweda.pipe(landLeft(1), landRight(1), landLeft(2))([0,0]);
console.log(result);
// => [3, 1]

还差一个判断皮尔斯是否掉下来的操做.

var landLeft = eweda.curry(function(n, pole){
    if(pole==='dead') return pole;
    if(Math.abs(pole[0]-pole[1]) > 3)
      return 'dead';
    return [pole[0]+n, pole[1]];
});
var landRight = eweda.curry(function(n, pole){
    if(pole==='dead') return pole;
    return landLeft(n, eweda.reverse(pole));
});
var result = eweda.pipe(landLeft(10), landRight(1), landRight(8))([0,0]);
console.log(result);
// => dead

完整代码


如今来试试用 Either

咱们先把皮尔斯放进 Either 盒子里, 这样皮尔斯的状态只有打开 Either 才能看见. 假设 Either Right 是活着, Left 的话皮尔斯挂了.

var land = eweda.curry(function(lr, n, pole){
    pole[lr] = pole[lr] + n;
    if(Math.abs(pole[0]-pole[1]) > 3) {
      return new Left("dead when land " + n + " became " + pole);
    }
    return new Right(pole);
});

var landLeft = land(0)
var landRight = land(1);

如今落鸟后会返回一个 Either, 要不活着, 要不挂了. 打开盒子的函数能够是这样的

var stillAlive = function(x){
    console.log(x)
}
var dead = function(x){
    console.log('皮尔斯' + x);
}
either(dead, stillAlive, landLeft(2, [0,0]))

好吧, 好像有一点点像了, 可是这只落了一次鸟, 若是我要落好几回呢. 这就须要实现 Either 的 >>= bind 方法了, 若是你还记得前面实现的 Functor, 这里很是像 :

var Monad = function(type, defs) {
  for (name in defs){
    type.prototype[name] = defs[name];
  }
  return type;
};
function Left(value){
  this.value = value
}
function Right(value){
  this.value=value;
}

Monad(Right, {
  bind:function(fn){
    return fn(this.value)
  }
})

Monad(Left, {
  bind: function(fn){
    return this;
  }
})

哦, 对了, either:

either = function(left, right, either){
    if(either.constructor.name === 'Right')
        return right(either.value)
    else
        return left(either.value)
}

咱们来试试工做不工做.

var walkInLine = new Right([0,0]);
eitherDeadOrNot = walkInLine.bind(landLeft(2))
    .bind(landRight(5))
either(dead, stillAlive, eitherDeadOrNot)
// => [2,5]
eitherDeadOrNot = walkInLine.bind(landLeft(2))
  .bind(landRight(5))
  .bind(landLeft(3))
  .bind(landLeft(10)
  .bind(landRight(10)))

either(dead, stillAlive, eitherDeadOrNot)
// => "皮尔斯dead when land 10 became 15,5"

完整代码

到底有什么用呢, Monad

咱们来总结下两种作法有什么区别:

  1. 通常作法每次都会检查查尔斯挂了没挂, 也就是重复得到以前操做的 context

  2. Monad 不对异常作处理, 只是不停地往盒子里加操做. 你能够看到对错误的处理推到了最后取值的 either.

  3. Monad 互相传递的只是盒子, 而通常写法会把异常往下传如"dead", 这样致使后面的操做都得先判断这个异常.

comment 因为是用 JavaScript, pole 不限定类型, 因此这里单纯的用字符串表明 pole 的异常状态. 但若是换成强类型的 Java, 可能实现就没这么简单了.

看来已经优点已经逐步明显了呢, Monad 里面保留了值的 context, 也就是咱们对这个 Monad 能够集中在单独的本次如何操做value, 而不用关心 context.

还有一个 Monad 叫作 Maybe, 实际上皮尔斯的

Monad 在 JavaScript 中的应用

你知道 ES6有个新的 类型 Promise 吗, 若是不知道, 想必也听过 jQuery 的 $.ajax吧, 但若是你没听过 promise, 说明你没有认真看过他的返回值:

var aPromise = $.ajax({
    url: "https://api.github.com/users/jcouyang/gists"
    dataType: 'jsonp'
    })
aPromise /***
=> Object { state: .Deferred/r.state(),
    always: .Deferred/r.always(),
    then: .Deferred/r.then(),
    promise: .Deferred/r.promise(),
    pipe: .Deferred/r.then(),
    done: b.Callbacks/p.add(),
    fail: b.Callbacks/p.add(),
    progress: b.Callbacks/p.add() }
***/

咱们看到返回了好多Deferred类型的玩意, 咱们来试试这玩意有什么用

anotherPromise = aPromise.then(_ => _.data.forEach(y=> console.log(y.description)))
/* =>
Object { state: .Deferred/r.state(),
    always: .Deferred/r.always(),
    then: .Deferred/r.then(),
    promise: .Deferred/r.promise(),
    pipe: .Deferred/r.then(),
    done: b.Callbacks/p.add(),
    fail: b.Callbacks/p.add(),
    progress: b.Callbacks/p.add() }

"connect cisco anyconnect in terminal"
"为何要柯里化(curry)"
"批量获取人人影视下载连接"
......
*/

看见没有, 他又返回了一样一个东西, 并且传给 then 的函数能够操做这个对象里面的值. 这个对象其实就是 Promise 了. 为何说这是 Monad 呢, 来试试再写一次走钢丝:

这里咱们用的是 ES6 的 Promise, 而不用 jQuery Defered, 记得用 firefox 哦. 另外 eweda 能够这样装

var ewd = document.createElement('script'); ewd.type = 'text/javascript'; ewd.async = true;
            ewd.src = 'https://rawgit.com/CrossEye/eweda/master/eweda.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(ewd);

eweda.installTo(this); //安装到 window 上
var land = curry(function(lr, n, pole){
    pole[lr] = pole[lr] + n;
    if(Math.abs(pole[0]-pole[1]) > 3) {
      return new Promise((resovle,reject)=>reject("dead when land " + n + " became " + pole));
    }
    return new Promise((resolve,reject)=>resolve(pole));
});

var landLeft = land(0)
var landRight = land(1);

Promise.all([0,0])
.then(landLeft(2), _=>_)
.then(landRight(3), _=>_) // => Array [ 2, 3 ]
.then(landLeft(10), _=>_)
.then(landRight(10), _=>_)
.then(_=>console.log(_),_=>console.log(_))
// => "dead when land 10 became 12,3"

这下是不认可 Promise 就是 Monad 了. 原来咱们早已在使用这个神秘的 Monad, 再想一想 Promise,也没有那么抽象和神秘了.

ref: Functional JavaScript 第四章

相关文章
相关标签/搜索