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