你造 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 第四章

相關文章
相關標籤/搜索