转载请说明出处: http://hai.li/2017/03/27/prom…
背景
上篇文章 函数式JS: 一种continuation monad推导 获得了一个相似promise的链式挪用,引发了如许的思索:岂非promise是monad?假如是的话又是怎样的monad呢?来来来,哥哥带你推倒,哦,不,是推导一下!
Monad
Monad是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
的真面目了。同学们!这道题!必考题!头两年不考,本年一定考!
组织推导unit
function 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
是用回调返回值的。也就是将一个用回调返回效果的函数作为容器。
组织推导flatMap
function 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