摘要:但有時候,所有的鳥都會想要停在同一邊,皮爾斯就失去了平衡,就會讓他從鋼索上掉下去。我們這邊假設兩邊的鳥差異在三個之內的時候,皮爾斯仍能保持平衡。
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 方法.
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我們來總結下兩種做法有什么區別:
一般做法每次都會檢查查爾斯掛了沒掛, 也就是重復獲得之前操作的 context
Monad 不對異常做處理, 只是不停地往盒子里加操作. 你可以看到對錯誤的處理推到了最后取值的 either.
Monad 互相傳遞的只是盒子, 而一般寫法會把異常往下傳如"dead", 這樣導致后面的操作都得先判斷這個異常.
comment 由于是用 JavaScript, pole 不限定類型, 所以這里單純的用字符串代表 pole 的異常狀態. 但如果換成強類型的 Java, 可能實現就沒這么簡單了.
看來已經優勢已經逐步明顯了呢, Monad 里面保留了值的 context, 也就是我們對這個 Monad 可以集中在多帶帶的本次如何操作value, 而不用關心 context.
Monad 在 JavaScript 中的應用還有一個 Monad 叫做 Maybe, 實際上皮爾斯的
你知道 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 第四章
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/87594.html
摘要:定義這是類型簽名的表述。實際上對應著,只是在里作為立即量傳入,在和的返回值中作為閉包引用傳入。同時根據看出返回值是用回調返回值的。的輸出是的包裹。的方法借助了閉包引用額外輸入了,而輸入的函數輸入是輸出則是借助實現的。 轉載請注明出處: http://hai.li/2017/03/27/prom... 背景 上篇文章 函數式JS: 一種continuation monad推導 得到了一個...
摘要:匿名函數是我們喜歡的一個重要原因,也是,它們分別消除了很多代碼細節上需要命名變量名或函數名的需要。這個匿名函數內,有更多的操作,根據的結果針對目錄和文件做了不同處理,而且有遞歸。 能和微博上的 @響馬 (fibjs作者)掰扯這個問題是我的榮幸。 事情緣起于知乎上的一個熱貼,諸神都發表了意見: https://www.zhihu.com/questio... 這一篇不是要說明白什么是as...
摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。寫一個符合規范并可配合使用的寫一個符合規范并可配合使用的理解的工作原理采用回調函數來處理異步編程。 JavaScript怎么使用循環代替(異步)遞歸 問題描述 在開發過程中,遇到一個需求:在系統初始化時通過http獲取一個第三方服務器端的列表,第三方服務器提供了一個接口,可通過...
閱讀 2183·2021-11-19 09:40
閱讀 1919·2021-11-08 13:24
閱讀 2453·2021-10-18 13:24
閱讀 2858·2021-10-11 10:57
閱讀 3578·2021-09-22 15:42
閱讀 1114·2019-08-29 17:11
閱讀 2528·2019-08-29 16:11
閱讀 2421·2019-08-29 11:11