轉評:你造promise就是monad嗎

  看到一遍好文章,與個人想法一模一樣,先轉爲敬。首先說說我對Monad和promise的理解:javascript

  Monad的這種抽象方式是爲了簡化程序中不肯定狀態的判斷而提出的,可以讓程序員從更高的層次順序描述程序邏輯的每個動做,而沒必要關注每個動做是否會出現異常,也沒必要關注第一個動做內是否須要邏輯判斷,是否要跳轉。haskell趣學指南我翻了好幾遍,終於對Monad這個概念有了一點認識,這個抽象是偉大的,它極大提升了程序的可讀性,同時下降了開發難度。這裏還要推薦一下haskell這門語言,Monad的概念就是haskell提出的,haskell的思想徹底不一樣於傳統的oo語言,學習過以後,你的編程世界觀會有一個轉變,原來程序還能這麼寫呀,而後回頭再寫oo語言時,效率會比原來有質的飛躍。php

  再說promise,常常寫前端的朋友應該對這個東西不陌生,前端語言如js,flash等常常要向後端發送ajax請求,當多個ajax嵌套在一塊兒時,回調函數大坑簡直讓人崩潰,promise就是爲了解決這個問題而發明的。用了promise,今後能夠優雅的發送ajax,串行發,並行發,多層嵌套發,想怎麼發就怎麼發。用了這麼長時間的promise,我終於發現,原來promise就是Monad啊。html

 

如下是原文,做者把Haskell趣學指南中的代碼翻譯成Js,完美闡釋了Monad的概念,贊!前端

 

Monad 這個概念好難解釋, 你能夠理解爲一個 Lazy 或者是狀態未知的盒子. 聽起來像是薛定諤貓(估計點進去你會更暈了). 其實就是的, 在你打開這個盒子以前, 你是不知道里面的貓處在那種狀態.java

Monad 這個黑盒子, 裏面到底賣的神馬藥,咱們要打開喝了才知道.git

等等, 不是說好要解釋 Either 的嗎, 嗯嗯, 這裏就是在解釋 Either. 上節說 Either 是一個 Functor, 能夠被 fmap over. 怎麼這裏又說道黑盒子了? 好吧, Monad 其實也是 Functor. 還記得我說的 Functor 實際上是一個帶 context 的盒子嗎. 而 fmap 使得往盒子裏應用函數變換成爲了可能.程序員

Either

先來看看 Either 這種類型會幹什麼事情. Either 表示要不是左邊就是右邊的值, 所以咱們能夠用它來表示薛定諤貓, 要不是活着, 要不死了. Either 還有個方法:
eithergithub

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

想必你已經對箭頭->很是熟了吧.若是前面幾章你都跳過了,我再翻譯下好了. 這裏表示接收函數a->c和函數b->c, 再接收一個 Either, 若是 Either 的值在左邊,則使用函數映射a->c, 若值在右邊,則應用第二個函數映射b->c.ajax

做爲 Monad, 它還必須具有一個方法 '>>='(這個符號好眼熟的說, 看看 haskell 的 logo, 你就知道 Monad 是有多重要), 也就是 bind 方法.編程

 

bind 方法的意思很簡單, 就是給這個盒子加一個操做, 好比往盒子在加放射性原子,若是貓活着,就是綠巨貓, 若是貓是死的,那就是綠巨死貓.

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

這有個毛用啊. 表急... 來看個經典例子

走鋼索

皮爾斯決定要辭掉他的工做改行試着走鋼索。他對走鋼索蠻在行的,不過仍有個小問題。就是鳥會停在他拿的平衡竿上。他們會飛過來停一小會兒,而後再飛走。這樣的狀況在兩邊的鳥的數量同樣時並非個太大的問題。但有時候,全部的鳥都會想要停在同一邊,皮爾斯就失去了平衡,就會讓他從鋼索上掉下去。

咱們這邊假設兩邊的鳥差別在三個以內的時候,皮爾斯仍能保持平衡。

通常解法

首先看看不用 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.
2. 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,也沒有那麼抽象和神祕了.

相關文章
相關標籤/搜索