摘要:定義這是類型簽名的表述。實(shí)際上對(duì)應(yīng)著,只是在里作為立即量傳入,在和的返回值中作為閉包引用傳入。同時(shí)根據(jù)看出返回值是用回調(diào)返回值的。的輸出是的包裹。的方法借助了閉包引用額外輸入了,而輸入的函數(shù)輸入是輸出則是借助實(shí)現(xiàn)的。
轉(zhuǎn)載請(qǐng)注明出處: http://hai.li/2017/03/27/prom...
背景上篇文章 函數(shù)式JS: 一種continuation monad推導(dǎo) 得到了一個(gè)類似promise的鏈?zhǔn)秸{(diào)用,引發(fā)了這樣的思考:難道promise是monad?如果是的話又是怎樣的monad呢?來來來,哥哥帶你推倒,哦,不,是推導(dǎo)一下!
MonadMonad是haskell里很重要的概念,作為一種類型,有著固定的操作方法,簡單的可以類比面向?qū)ο蟮慕涌凇?/p> 定義
unit :: a -> Monad a flatMap :: Monad a -> (a -> Monad b) -> Monad b
這是類型簽名的表述。unit的作用可以理解為將a放入容器中變成Monad a。而當(dāng)flatMap轉(zhuǎn)為(a -> Monad b) -> (Monad a -> Monad b)時(shí),它的作用就可以理解為將a -> Monad b函數(shù)轉(zhuǎn)換成Monad a -> Monad b函數(shù)。
法則flatMap(unit(x), f) ==== f(x) //左單位元 flatMap(monad, unit) ==== monad //右單位元 flatMap(flatMap(monad, f), g) ==== flatMap(monad, function(x) { flatMap(f(x), g) }) //關(guān)聯(lián)性
這里x是一般的值,f和g是一般的函數(shù),monad是一個(gè)Monad類型的值,可以這么理解:
左單位元法則就是將包裹unit(x)和函數(shù)f傳給flatMap執(zhí)行等價(jià)于將包裹中的值x抽出傳給函數(shù)f執(zhí)行
右單位元法則就是將包裹monad和函數(shù)unit傳給flatMap執(zhí)行等價(jià)于包裹monad本身(有點(diǎn)像1*1=1)
關(guān)聯(lián)性法則就是將包裹monad和函數(shù)f傳給flatMap執(zhí)行,再將執(zhí)行的結(jié)果和函數(shù)g傳給flatMap執(zhí)行等價(jià)于將包裹monad中的值x抽出傳給f執(zhí)行(執(zhí)行結(jié)果依然是Monad類型),再將執(zhí)行結(jié)果中的值x抽出傳給g執(zhí)行
Promise 鏈?zhǔn)秸{(diào)用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鏈?zhǔn)秸{(diào)用整理一下,將關(guān)注點(diǎn)集中在鏈?zhǔn)秸{(diào)用上
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的特性可以直觀地對(duì)應(yīng)為
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)
而對(duì)象的方法可以通過將this作為參數(shù)傳入方便地轉(zhuǎn)為直接的函數(shù),比如
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")
這樣將鏈?zhǔn)秸{(diào)用轉(zhuǎn)為嵌套函數(shù)調(diào)用變成
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這兩個(gè)直接函數(shù)可以構(gòu)造推導(dǎo)出來,就可以窺探Promise的真面目了。同學(xué)們!這道題!必考題!頭兩年不考,今年肯定考!
構(gòu)造推導(dǎo)unitfunction unit(f){ return f}
由flatMap :: Monad a -> (a -> Monad b) -> Monad b和flatMap(unit(g0))(g1)可知傳入g1的參數(shù)就是a,對(duì)應(yīng)著"0"。
但由unit :: a -> Monad a和unit(g0)得到的a卻對(duì)應(yīng)著g0。實(shí)際上a對(duì)應(yīng)著"0",只是a在g0里作為立即量傳入,在g1和g2的返回值中作為閉包引用傳入。
Monad可看作容器,那用什么做的容器呢?既然作為參數(shù)傳入unit的函數(shù)f已經(jīng)包裹了a,那試試直接作為Monad a返回。同時(shí)根據(jù)g0看出返回值f是用回調(diào)返回值的。也就是將一個(gè)用回調(diào)返回結(jié)果的函數(shù)作為容器。
構(gòu)造推導(dǎo)flatMapfunction flatMap(ma){ return function(g) { var b=[], ga, h=[]; ma(function(a) { //1. 解包`ma`取出`a` ga = g(a); //2. 將`a`傳到`g`中執(zhí)行 (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. 將執(zhí)行結(jié)果`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返回函數(shù),這個(gè)函數(shù)接收(a -> Monad b)返回Monad b,而(a -> Monad b)對(duì)應(yīng)g1??梢詷?gòu)造flatMap如下
function flatMap(ma){return function(g1) { /*b = g1(a);*/ return mb }}
實(shí)際flatMap做了3步工作
解包ma取出a
將a傳到g1中執(zhí)行
將執(zhí)行結(jié)果b包裹成mb返回
這里ma和g1都是容器,通過回調(diào)得到輸出結(jié)果,所以在ma的回調(diào)中執(zhí)行g1(a),再在g1(a)的回調(diào)中得到執(zhí)行結(jié)果v,再將執(zhí)行結(jié)果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是立即執(zhí)行的話,第flatMap的執(zhí)行步驟是1--2--3,但如果2延遲執(zhí)行步驟就變成了1--3--2,算上下一個(gè)flatMap就是1.1--1.3--1.2--2.1。2.1的ma就是1.2的mb,2.1的ma的參數(shù)c中執(zhí)行了2.2和2.3,也就是1.3的c決定著2.1之后的步驟。如果將c賦值給b就可以在1.2執(zhí)行完后才繼續(xù)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可以鏈接多個(gè)flatMap,也就是一個(gè)1.3被多個(gè)2.1消化,需要保存所有在2.1后的執(zhí)行鏈 c,用數(shù)組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立即執(zhí)行和延遲執(zhí)行情況,同時(shí)適配多個(gè)1.3被多個(gè)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,所以還要加上對(duì)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 }) } }
現(xiàn)在可以測試下代碼了
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`中執(zhí)行 (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. 將執(zhí)行結(jié)果`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)整合代碼
現(xiàn)在將嵌套函數(shù)變回鏈?zhǔn)秸{(diào)用,這里也可以用是否有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`中執(zhí)行 (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. 將執(zhí)行結(jié)果`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中的空格請(qǐng)問哪里有差?雖然改成構(gòu)造函數(shù)使得newPromise改成new Promise也是分分鐘的事情,但重點(diǎn)不是這個(gè),重點(diǎn)是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的輸入是一個(gè)函數(shù),但在推導(dǎo)構(gòu)造unit里解釋過,這里借助了立即量和閉包引用來額外增加了輸入a,newPromise的參數(shù)則作為構(gòu)造unit的補(bǔ)充邏輯。
newPromise的輸出是a的包裹Monad a。
newPromise的方法then借助了閉包引用額外輸入了Monad a,而輸入的g函數(shù)輸入是a輸出則是借助newPromise實(shí)現(xiàn)的Monad b。
newPromise的方法then輸出的是借助newPromise實(shí)現(xiàn)的Monad b,這里和g的輸出Monad b不是同一個(gè)Monad但是b確實(shí)相同的。
符合法則flatMap(unit(x), f) === f(x) //左單位元 flatMap(monad, unit) === monad //右單位元 flatMap(flatMap(monad, f), g) === flatMap(monad, function(x) { flatMap(f(x), g) }) //關(guān)聯(lián)性
將法則改下名,同時(shí)改為鏈?zhǔn)秸{(diào)用
newPromise(x).then(f) ==== f(x) //左單位元 monad.then(newPromise) ==== monad //右單位元 monad.then(f).then(g) ==== monad.then(function(x) { f(x).then(g) }) //關(guān)聯(lián)性
左單位元法則驗(yàn)證代碼如下:
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
右單位元法則驗(yàn)證代碼如下:
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
關(guān)聯(lián)性法則驗(yàn)證代碼如下:
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函數(shù)式編程指南
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
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/82234.html
摘要:組合組合的功能非常強(qiáng)大,也是函數(shù)式編程的一個(gè)核心概念,所謂的對(duì)過程進(jìn)行封裝很大程度上就是依賴于組合。在理解之前,先認(rèn)識(shí)一個(gè)東西概念容器容器為函數(shù)式編程里普通的變量對(duì)象函數(shù)提供了一層極其強(qiáng)大的外衣,賦予了它們一些很驚艷的特性。 前言 JavaScript是一門多范式語言,即可使用OOP(面向?qū)ο螅?,也可以使用FP(函數(shù)式),由于筆者最近在學(xué)習(xí)React相關(guān)的技術(shù)棧,想進(jìn)一步深入了解其思想...
摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復(fù)雜性。寫一個(gè)符合規(guī)范并可配合使用的寫一個(gè)符合規(guī)范并可配合使用的理解的工作原理采用回調(diào)函數(shù)來處理異步編程。 JavaScript怎么使用循環(huán)代替(異步)遞歸 問題描述 在開發(fā)過程中,遇到一個(gè)需求:在系統(tǒng)初始化時(shí)通過http獲取一個(gè)第三方服務(wù)器端的列表,第三方服務(wù)器提供了一個(gè)接口,可通過...
摘要:但有時(shí)候,所有的鳥都會(huì)想要停在同一邊,皮爾斯就失去了平衡,就會(huì)讓他從鋼索上掉下去。我們這邊假設(shè)兩邊的鳥差異在三個(gè)之內(nèi)的時(shí)候,皮爾斯仍能保持平衡。 Monad 這個(gè)概念好難解釋, 你可以理解為一個(gè) Lazy 或者是狀態(tài)未知的盒子. 聽起來像是薛定諤貓(估計(jì)點(diǎn)進(jìn)去你會(huì)更暈了). 其實(shí)就是的, 在你打開這個(gè)盒子之前, 你是不知道里面的貓?zhí)幵谀欠N狀態(tài). Monad 這個(gè)黑盒子, 里面到底賣的神...
摘要:本文是響應(yīng)式編程第二章序列的深入研究這篇文章的學(xué)習(xí)筆記。函數(shù)科里化的基本應(yīng)用,也是函數(shù)式編程中運(yùn)算管道構(gòu)建的基本方法。四資料參考函數(shù)式編程指南 本文是Rxjs 響應(yīng)式編程-第二章:序列的深入研究這篇文章的學(xué)習(xí)筆記。示例代碼托管在:http://www.github.com/dashnowords/blogs 更多博文:《大史住在大前端》目錄 showImg(https://segme...
閱讀 3831·2021-11-24 10:46
閱讀 1789·2021-11-15 11:38
閱讀 3852·2021-11-15 11:37
閱讀 3683·2021-10-27 14:19
閱讀 2040·2021-09-03 10:36
閱讀 2064·2021-08-16 11:02
閱讀 3065·2019-08-30 15:55
閱讀 2328·2019-08-30 15:44