摘要:定義這是類型簽名的表述。實際上對應著,只是在里作為立即量傳入,在和的返回值中作為閉包引用傳入。同時根據看出返回值是用回調返回值的。的輸出是的包裹。的方法借助了閉包引用額外輸入了,而輸入的函數輸入是輸出則是借助實現的。
轉載請注明出處: http://hai.li/2017/03/27/prom...
背景上篇文章 函數式JS: 一種continuation monad推導 得到了一個類似promise的鏈式調用,引發了這樣的思考:難道promise是monad?如果是的話又是怎樣的monad呢?來來來,哥哥帶你推倒,哦,不,是推導一下!
MonadMonad是haskell里很重要的概念,作為一種類型,有著固定的操作方法,簡單的可以類比面向對象的接口。
定義unit :: a -> Monad a flatMap :: Monad a -> (a -> Monad b) -> Monad b
這是類型簽名的表述。unit的作用可以理解為將a放入容器中變成Monad a。而當flatMap轉為(a -> Monad b) -> (Monad a -> Monad b)時,它的作用就可以理解為將a -> Monad b函數轉換成Monad a -> Monad b函數。
法則flatMap(unit(x), f) ==== f(x) //左單位元 flatMap(monad, unit) ==== monad //右單位元 flatMap(flatMap(monad, f), g) ==== flatMap(monad, function(x) { flatMap(f(x), g) }) //關聯性
這里x是一般的值,f和g是一般的函數,monad是一個Monad類型的值,可以這么理解:
左單位元法則就是將包裹unit(x)和函數f傳給flatMap執行等價于將包裹中的值x抽出傳給函數f執行
右單位元法則就是將包裹monad和函數unit傳給flatMap執行等價于包裹monad本身(有點像1*1=1)
關聯性法則就是將包裹monad和函數f傳給flatMap執行,再將執行的結果和函數g傳給flatMap執行等價于將包裹monad中的值x抽出傳給f執行(執行結果依然是Monad類型),再將執行結果中的值x抽出傳給g執行
Promise 鏈式調用new Promise(function(resolve) { setTimeout(function() { resolve("0") }, 100) }) .then(function(v) { console.log(v); return new Promise(function(resolve) { resolve(v + "->1") }) }) .then(function(v) { console.log(v); return new Promise(function(resolve) { setTimeout(function() { resolve(v + "->2") }, 1000) }) }) .then(console.log)分析
先將Promise鏈式調用整理一下,將關注點集中在鏈式調用上
function f0(resolve) { setTimeout(function() { resolve("0") }, 100) } function f1(v) { console.log(v); return new Promise(function(resolve) { resolve(v + "->1") }) } function f2(v) { console.log(v); return new Promise(function(resolve) { setTimeout(function() { resolve(v + "->2") }, 1000) }) } function f3(v) { console.log(v) } new Promise(f0).then(f1).then(f2).then(f3) //0 0->1 0->1->2
從unit和flatMap的特性可以直觀地對應為
function g0(resolve) { setTimeout(function() { resolve("0") }, 100) } function g1(v) { console.log(v); return unit(function(resolve) { resolve(v + "->1") }) } function g2(v) { console.log(v); return unit(function(resolve) { setTimeout(function() { resolve(v + "->2") }, 1000) }) } function g3(v) { console.log(v) } unit(g0).flatMap(g1).flatMap(g2).flatMap(f3)
而對象的方法可以通過將this作為參數傳入方便地轉為直接的函數,比如
var a = {method: function f(v){ console.log(this, v) }} var a_method = function(t, v){ console.log(t, v) } a.method("a") === a_method(a, "a")
這樣將鏈式調用轉為嵌套函數調用變成
function g0(resolve) { setTimeout(function() { resolve("0") }, 100) } function g1(v) { console.log(v); return unit(function(resolve) { resolve(v + "->1") }) } function g2(v) { console.log(v); return unit(function(resolve) { setTimeout(function() { resolve(v + "->2") }, 1000) }) } function g3(v) { console.log(v) } flatMap( flatMap( flatMap( unit(g0) )(g1) )(g2) )(g3)
這樣如果unit和flatMap這兩個直接函數可以構造推導出來,就可以窺探Promise的真面目了。同學們!這道題!必考題!頭兩年不考,今年肯定考!
構造推導unitfunction unit(f){ return f}
由flatMap :: Monad a -> (a -> Monad b) -> Monad b和flatMap(unit(g0))(g1)可知傳入g1的參數就是a,對應著"0"。
但由unit :: a -> Monad a和unit(g0)得到的a卻對應著g0。實際上a對應著"0",只是a在g0里作為立即量傳入,在g1和g2的返回值中作為閉包引用傳入。
Monad可看作容器,那用什么做的容器呢?既然作為參數傳入unit的函數f已經包裹了a,那試試直接作為Monad a返回。同時根據g0看出返回值f是用回調返回值的。也就是將一個用回調返回結果的函數作為容器。
構造推導flatMapfunction flatMap(ma){ return function(g) { var b=[], ga, h=[]; ma(function(a) { //1. 解包`ma`取出`a` ga = g(a); //2. 將`a`傳到`g`中執行 (ga && ga.flatMap ? ga : unit(function(c) { c(ga) })) //處理g沒返回unit情況 (function(v) { b.push(v); // 1.1—1.2—1.3 h.map(function(c) {c(v)}) //1.1—1.3—1.2 }) }); return unit(function(c) { //3. 將執行結果`b`包裹成`mb`返回 b.length ? b.map(c) // 1.1—1.2—1.3—2.1 : h.push(c) //1.1—1.3—1.2—2.1 }) } }
由flatMap :: Monad a -> (a -> Monad b) -> Monad b知道flatMap傳入Monad a返回函數,這個函數接收(a -> Monad b)返回Monad b,而(a -> Monad b)對應g1。可以構造flatMap如下
function flatMap(ma){return function(g1) { /*b = g1(a);*/ return mb }}
實際flatMap做了3步工作
解包ma取出a
將a傳到g1中執行
將執行結果b包裹成mb返回
這里ma和g1都是容器,通過回調得到輸出結果,所以在ma的回調中執行g1(a),再在g1(a)的回調中得到執行結果v,再將執行結果v賦值給外部變量b,最后將b用unit包裹成Monad b返回。
function flatMap(ma){ return function(g1) { var b; ma(function(a) { g1(a)(function(v) { b = v }) }); return unit(function(c) {c(b)}) } }
如果g1是立即執行的話,第flatMap的執行步驟是1--2--3,但如果2延遲執行步驟就變成了1--3--2,算上下一個flatMap就是1.1--1.3--1.2--2.1。2.1的ma就是1.2的mb,2.1的ma的參數c中執行了2.2和2.3,也就是1.3的c決定著2.1之后的步驟。如果將c賦值給b就可以在1.2執行完后才繼續2.1之后的步驟,也就是:
+--------------+ 1.1—1.2—1.3—2.1 2.2—2.3
function flatMap(ma){ return function(g1) { var b; ma(function(a) { g1(a)(function(v) { b(v) }) }); return unit(function(c) { b = c }) } }
為了flatMap可以鏈接多個flatMap,也就是一個1.3被多個2.1消化,需要保存所有在2.1后的執行鏈 c,用數組h解決。
function flatMap(ma){ return function(g1) { var h=[]; ma(function(a) { g1(a)(function(v) { h.map(function(c) {c(v)}) }) }); return unit(function(c) { h.push(c) }) } }
整合1.2立即執行和延遲執行情況,同時適配多個1.3被多個2.1消化的情況,代碼如下:
function flatMap(ma){ return function(g1) { var b=[], h=[]; ma(function(a) { g1(a)(function(v) { b.push(v); // 1.1—1.2—1.3 h.map(function(c) {c(v)}) //1.1—1.3—1.2 }) }); return unit(function(c) { b.length ? b.map(c) // 1.1—1.2—1.3—2.1 : h.push(c) //1.1—1.3—1.2—2.1 }) } }
由于g3沒有返回mb,所以還要加上對g1返回的不是容器的處理,代碼如下:
function flatMap(ma){ return function(g1) { var b=[], g1a, h=[]; ma(function(a) { g1a = g1(a); (g1a && typeof(g1a) == "function" ? g1a : unit(function(c) { c(g1a) })) (function(v) { b.push(v); // 1.1—1.2—1.3 h.map(function(c) {c(v)}) //1.1—1.3—1.2 }) }); return unit(function(c) { b.length ? b.map(c) // 1.1—1.2—1.3—2.1 : h.push(c) //1.1—1.3—1.2—2.1 }) } }
現在可以測試下代碼了
function unit(f){ return f } function flatMap(ma) { return function(g) { var b=[], ga, h=[]; ma(function(a) { //1. 解包`ma`取出`a` ga = g(a); //2. 將`a`傳到`g`中執行 (ga && typeof(ga) == "function"? ga : unit(function(c) { c(ga) })) //處理g沒返回unit情況 (function(v) { b.push(v); // 1.1—1.2—1.3 h.map(function(c) {c(v)}) //1.1—1.3—1.2 }) }); return unit(function(c) { //3. 將執行結果`b`包裹成`mb`返回 b.length ? b.map(c) // 1.1—1.2—1.3—2.1 : h.push(c) //1.1—1.3—1.2—2.1 }) } } function g0(resolve) { setTimeout(function() { resolve("0") }, 100) } function g1(v) { console.log(v); return unit(function(resolve) { resolve(v + "->1") }) } function g2(v) { console.log(v); return unit(function(resolve) { setTimeout(function() { resolve(v + "->2") }, 1000) }) } function g3(v) { console.log(v) } flatMap( flatMap( flatMap( unit(g0) )(g1) )(g2) )(g3)整合代碼
現在將嵌套函數變回鏈式調用,這里也可以用是否有flatMap方法來判斷g1是否返回容器
function unit(ma) { ma.flatMap = function(g){ var b=[], ga, h=[]; ma(function(a) { //1. 解包`ma`取出`a` ga = g(a); //2. 將`a`傳到`g`中執行 (ga && ga.flatMap ? ga : unit(function(c) { c(ga) })) //處理g沒返回unit情況 (function(v) { b.push(v); // 1.1—1.2—1.3 h.map(function(c) {c(v)}) //1.1—1.3—1.2 }) }); return unit(function(c) { //3. 將執行結果`b`包裹成`mb`返回 b.length ? b.map(c) // 1.1—1.2—1.3—2.1 : h.push(c) //1.1—1.3—1.2—2.1 }) } return ma } function g0(resolve) { setTimeout(function() { resolve("0") }, 100) } function g1(v) { console.log(v); return unit(function(resolve) { resolve(v + "->1") }) } function g2(v) { console.log(v); return unit(function(resolve) { setTimeout(function() { resolve(v + "->2") }, 1000) }) } function g3(v) { console.log(v) } unit(g0).flatMap(g1).flatMap(g2).flatMap(g3)Promise是Monad嗎?
將整合代碼中unit改成newPromise,flatMap改成then,哇塞,除了new Promise中的空格請問哪里有差?雖然改成構造函數使得newPromise改成new Promise也是分分鐘的事情,但重點不是這個,重點是Promise是Monad嗎?粗看是!
function newPromise(ma) { ma.then = function(g){ var b=[], ga, h=[]; ma(function(a) { ga = g(a); (ga && ga.then ? ga : newPromise(function(c) { c(ga) })) (function(v) { b.push(v); h.map(function(c) {c(v)}) }) }); return newPromise(function(c) { b.length ? b.map(c) : h.push(c) }) } return ma } newPromise(function(resolve) { setTimeout(function() { resolve("0") }, 100) }) .then(function(v) { console.log(v); return newPromise(function(resolve) { resolve(v + "->1") }) }) .then(function(v) { console.log(v); return newPromise(function(resolve) { setTimeout(function() { resolve(v + "->2") }, 1000) }) }) .then(console.log)符合定義
unit :: a -> Monad a flatMap :: Monad a -> (a -> Monad b) -> Monad b
將定義改下名
newPromise :: a -> Monad a then :: Monad a -> (a -> Monad b) -> Monad b
newPromise的輸入是一個函數,但在推導構造unit里解釋過,這里借助了立即量和閉包引用來額外增加了輸入a,newPromise的參數則作為構造unit的補充邏輯。
newPromise的輸出是a的包裹Monad a。
newPromise的方法then借助了閉包引用額外輸入了Monad a,而輸入的g函數輸入是a輸出則是借助newPromise實現的Monad b。
newPromise的方法then輸出的是借助newPromise實現的Monad b,這里和g的輸出Monad b不是同一個Monad但是b確實相同的。
符合法則flatMap(unit(x), f) === f(x) //左單位元 flatMap(monad, unit) === monad //右單位元 flatMap(flatMap(monad, f), g) === flatMap(monad, function(x) { flatMap(f(x), g) }) //關聯性
將法則改下名,同時改為鏈式調用
newPromise(x).then(f) ==== f(x) //左單位元 monad.then(newPromise) ==== monad //右單位元 monad.then(f).then(g) ==== monad.then(function(x) { f(x).then(g) }) //關聯性
左單位元法則驗證代碼如下:
function newPromise(ma) { ma.then = function(g){ var b=[], ga, h=[]; ma(function(a) { ga = g(a); (ga && ga.then ? ga : newPromise(function(c) { c(ga) })) (function(v) { b.push(v); h.map(function(c) {c(v)}) }) }); return newPromise(function(c) { b.length ? b.map(c) : h.push(c) }) } return ma } var x = 1; var f = function(v){ return v + 2 } newPromise(function(resolve) { resolve(x) }).then(f).then(console.log) //3 console.log(f(x)) //3
右單位元法則驗證代碼如下:
function newPromise(ma) { ma.then = function(g){ var b=[], ga, h=[]; ma(function(a) { ga = g(a); (ga && ga.then ? ga : newPromise(function(c) { c(ga) })) (function(v) { b.push(v); h.map(function(c) {c(v)}) }) }); return newPromise(function(c) { b.length ? b.map(c) : h.push(c) }) } return ma } newPromise(function(resolve) { resolve(1) }).then(newPromise).then(console.log) //1 newPromise(function(resolve) { resolve(1) }).then(console.log) //1
關聯性法則驗證代碼如下:
function newPromise(ma) { ma.then = function(g){ var b=[], ga, h=[]; ma(function(a) { ga = g(a); (ga && ga.then ? ga : newPromise(function(c) { c(ga) })) (function(v) { b.push(v); h.map(function(c) {c(v)}) }) }); return newPromise(function(c) { b.length ? b.map(c) : h.push(c) }) } return ma } var f = function(v) { return newPromise(function(resolve) { resolve(v+2) }) } var g = function(v) { return newPromise(function(resolve) { resolve(v+3) }) } newPromise(function(resolve) { resolve(1) }).then(f).then(g).then(console.log) //6 newPromise(function(resolve) { resolve(1) }).then(function(x) { return f(x).then(g) }).then(console.log) //6如此,原來Promise是這樣的Monad! 參考
Monads by Diagram
Monads and Gonads
Monad laws
A Fistful of Monads
Javascript Functor, Applicative, Monads in pictures
Functors and Applicatives
JS函數式編程指南
Translation from Haskell to JavaScript of selected portions of the best introduction to monads I’ve ever read
Flipping arrows in coBurger King
My angle on MonadsBackground
Awesomely descriptive JavaScript with monads
Monads in JavaScript
怎樣用簡單的語言解釋 monad?
如何解釋 Haskell 中的單子?
How do I return the response from an asynchronous call?
Promise
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/82234.html
摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。寫一個符合規范并可配合使用的寫一個符合規范并可配合使用的理解的工作原理采用回調函數來處理異步編程。 JavaScript怎么使用循環代替(異步)遞歸 問題描述 在開發過程中,遇到一個需求:在系統初始化時通過http獲取一個第三方服務器端的列表,第三方服務器提供了一個接口,可通過...
摘要:但有時候,所有的鳥都會想要停在同一邊,皮爾斯就失去了平衡,就會讓他從鋼索上掉下去。我們這邊假設兩邊的鳥差異在三個之內的時候,皮爾斯仍能保持平衡。 Monad 這個概念好難解釋, 你可以理解為一個 Lazy 或者是狀態未知的盒子. 聽起來像是薛定諤貓(估計點進去你會更暈了). 其實就是的, 在你打開這個盒子之前, 你是不知道里面的貓處在那種狀態. Monad 這個黑盒子, 里面到底賣的神...
摘要:本文是響應式編程第二章序列的深入研究這篇文章的學習筆記。函數科里化的基本應用,也是函數式編程中運算管道構建的基本方法。四資料參考函數式編程指南 本文是Rxjs 響應式編程-第二章:序列的深入研究這篇文章的學習筆記。示例代碼托管在:http://www.github.com/dashnowords/blogs 更多博文:《大史住在大前端》目錄 showImg(https://segme...
閱讀 3735·2021-11-24 10:46
閱讀 1706·2021-11-15 11:38
閱讀 3761·2021-11-15 11:37
閱讀 3481·2021-10-27 14:19
閱讀 1939·2021-09-03 10:36
閱讀 1991·2021-08-16 11:02
閱讀 2998·2019-08-30 15:55
閱讀 2251·2019-08-30 15:44