你造 Promise 就是 Monad 吗

Monad 这个观点好难诠释, 你能够理解为一个 Lazy 或者是状况未知的盒子. 听起来像是薛定谔猫(预计点进去你会更晕了). 实在就是的, 在你翻开这个盒子之前, 你是不晓得内里的猫处在那种状况.

Monad 这个黑盒子, 内里究竟卖的神马药,我们要翻开喝了才晓得.

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

Either

先来看看 Either 这类范例会干什么事情. Either 示意要不是左侧就是右侧的值, 因而我们能够用它来示意薛定谔猫, 要不是在世, 要不死了. Either 另有个要领:
either

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

想必你已对箭头->非常熟了吧.假如前面几章你都跳过了,我再翻译下好了. 这里示意吸收函数a->c和函数 b->c, 再吸收一个 Either, 假如 Either 的值在左侧,则运用函数映照 a->c, 若值在右侧,则运用第二个函数映照 b->c.

作为 Monad, 它还必需具有一个要领 ‘>>='(这个标记好眼熟的说, 看看 haskell 的 logo, 你就晓得 Monad 是有多主要), 也就是 bind 要领.

《你造 Promise 就是 Monad 吗》

bind 要领的意义很简朴, 就是给这个盒子加一个操纵, 比方往盒子在加放射性原子,假如猫在世,就是绿巨猫, 假如猫是死的,那就是绿巨死猫.

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

这有个毛用啊. 表急… 来看个典范例子

走钢索

皮尔斯决定要辞掉他的事情转业试着走钢索。他对走钢索蛮在行的,不过仍有个小题目。就是鸟会停在他拿的均衡竿上。他们会飞过来停一小会儿,然后再飞走。如许的状况在双方的鸟的数目一样时并非个太大的题目。但有时刻,一切的鸟都邑想要停在同一边,皮尔斯就失去了均衡,就会让他从钢索上掉下去。

《你造 Promise 就是 Monad 吗》

我们这边假定双方的鸟差别在三个以内的时刻,皮尔斯仍能坚持均衡。

平常解法

起首看看不必 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 第四章

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