函数式JS: 一种continuation monad推导
背景
js(javascript)揉合了面向对象和面向函数的特性,使用js解释如何从面向对象迁移到面向函数非常适合,这部分介绍js continuation monad的简明推导。
continuation monad
monad的一种,用于模式化cps(也就是回调风格),monad是函数型语言处理副作用的其中一种方式,可以理解为容器(见末尾参考)
定义
unit :: a -> monad a
bind :: monad a -> (a -> monad b) -> monad b
两个函数,unit函数输入一个参数a返回monad a,bind函数输入monad a和输入a返回monad b的函数,返回monad b
应用
这里使用一个callback hell的例子
//四个cps的异步函数
function getFoo(param ,next) {
setTimeout(function() {
console.log("getFoo: "+param);
next("foo<-"+param)
},100)
}
function getBar(param ,next) {
setTimeout(function() {
console.log("getBar: "+param);
next("bar<-"+param)
},100)
}
function getBaz(param ,next) {
setTimeout(function() {
console.log("getBaz: "+param);
next("baz<-"+param)
},100)
}
function getSna(param ,next) {
setTimeout(function() {
console.log("getSna: "+param);
next("sna<-"+param)
},100)
}
//通常按getFoo->getBar->getBaz->getSna的顺序调用,参数通过回调函数传入
function call(param ,next) {
getFoo(param ,function(foo) {
getBar(foo ,function(bar) {
getBaz(bar ,function(baz) {
getSna(baz ,next)
})
})
})
}
console.log("\ncall");
call("o",function(param){console.log("end: "+param)});
//使用unit和bind结合Array.prototype.reduce优雅解决callback hell
function call12(param ,next) {
function unit(arg) {
return function(f) {
f(arg)
}
}
function bind(M ,f) {
return function(next) {
M(function(arg) {
f(arg ,next)
})
}
}
[getFoo ,getBar ,getBaz ,getSna].reduce(bind ,unit(param))(next)
}
console.log("\ncall12");
call12("o",function(param){console.log("end: "+param)});
推导
0、最初
//按getFoo->getBar->getBaz->getSna的顺序调用,参数通过回调函数传入
function call(param ,next) {
getFoo(param ,function(foo) {//闭包引用getFoo,param
getBar(foo ,function(bar) {//闭包引用getBar
getBaz(bar ,function(baz) {//闭包引用getBaz
getSna(baz ,next)//闭包引用getSna,next
})
})
})
}
console.log("\ncall");
call("o",function(param){console.log("end: "+param)});
1、扁平
//将匿名函数转为引用,压扁嵌套回调,根据引用依赖倒置顺序
function call1(param ,next) {
var bazNext = function(baz) {//闭包引用getSna,next
getSna(baz ,next)
}
var barNext = function(bar) {//闭包引用getBaz,bazNext
getBaz(bar ,bazNext)
}
var fooNext = function(foo) {//闭包引用getBar,barNext
getBar(foo ,barNext)
}
getFoo(param ,fooNext)//闭包引用getFoo,param,fooNext
}
console.log("\ncall 1");
call1("o",function(param){console.log("end: "+param)});
2、fooNext闭包引用转参数引用
//fooNext闭包引用转为参数引用,隔离依赖,抽离调用
function call2(param ,next) {
var bazNext = function(baz) {//闭包引用getSna,next
getSna(baz ,next)
}
var barNext = function(bar) {//闭包引用getBaz,bazNext
getBaz(bar ,bazNext)
}
var fooNext = function(foo) {//闭包引用getBar,barNext
getBar(foo ,barNext)
}
var fooM = function(next) {//闭包引用getFoo,param
getFoo(param ,next)
}
fooM(fooNext)//闭包引用fooNext
}
console.log("\ncall 2");
call2("o",function(param){console.log("end: "+param)});
3、fooNext代入
//fooNext代入,调整调用顺序
function call3(param ,next) {
var bazNext = function(baz) {//闭包引用getSna,next
getSna(baz ,next)
}
var barNext = function(bar) {//闭包引用getBaz,bazNext
getBaz(bar ,bazNext)
}
var fooM = function(next) {//闭包引用getFoo,param
getFoo(param ,next)
}
fooM(function(foo) {//闭包引用getBar,barNext
getBar(foo ,barNext)
})
}
console.log("\ncall 3");
call3("o",function(param){console.log("end: "+param)});
4、barNext闭包引用转为参数引用
//barNext闭包引用转为参数引用,隔离依赖,抽离调用
function call4(param ,next) {
var bazNext = function(baz) {//闭包引用getSna,next
getSna(baz ,next)
}
var barNext = function(bar) {//闭包引用getBaz,bazNext
getBaz(bar ,bazNext)
}
var fooM = function(next) {//闭包引用getFoo,param
getFoo(param ,next)
}
var barM = function(next) {//闭包引用fooM,getBar
fooM(function(foo) {
getBar(foo ,next)
})
}
barM(barNext)//闭包引用barNext
}
console.log("\ncall 4");
call4("o",function(param){console.log("end: "+param)});
5、barNext代入
//barNext代入,调整调用顺序
function call5(param ,next) {
var bazNext = function(baz) {//闭包引用getSna,next
getSna(baz ,next)
}
var fooM = function(next) {//闭包引用getFoo,param
getFoo(param ,next)
}
var barM = function(next) {//闭包引用fooM,getBar
fooM(function(foo) {
getBar(foo ,next)
})
}
barM(function(bar) {//闭包引用getBaz,bazNext
getBaz(bar ,bazNext)
})
}
console.log("\ncall 5");
call5("o",function(param){console.log("end: "+param)});
6、bazNext闭包引用转参数引用
//bazNext闭包引用转为参数引用,隔离依赖,抽离调用
function call6(param ,next) {
var bazNext = function(baz) {//闭包引用getSna,next
getSna(baz ,next)
}
var fooM = function(next) {//闭包引用getFoo,param
getFoo(param ,next)
}
var barM = function(next) {//闭包引用fooM,getBar
fooM(function(foo) {
getBar(foo ,next)
})
}
var bazM = function(next) {//闭包引用barM,getBaz
barM(function(bar) {
getBaz(bar ,next)
})
}
bazM(bazNext)//闭包引用bazNext
}
console.log("\ncall 6");
call6("o",function(param){console.log("end: "+param)});
7、bazNext代入
//bazNext代入,调整调用顺序
function call7(param ,next) {
var fooM = function(next) {//闭包引用getFoo,param
getFoo(param ,next)
}
var barM = function(next) {//闭包引用fooM,getBar
fooM(function(foo) {
getBar(foo ,next)
})
}
var bazM = function(next) {//闭包引用barM,getBaz
barM(function(bar) {
getBaz(bar ,next)
})
}
bazM(function(baz) {//闭包引用getSna,next
getSna(baz ,next)
})
}
console.log("\ncall 7");
call7("o",function(param){console.log("end: "+param)});
8、bazNext闭包引用转参数引用
//bazNext闭包引用转为参数引用,隔离依赖,抽离调用
//现在getFoo,getBar,getBaz,getSna的依赖顺序调反过来了,且各自的next闭包引用转为参数引用
function call8(param ,next) {
var fooM = function(next) {//闭包引用getFoo,param
getFoo(param ,next)
}
var barM = function(next) {//闭包引用fooM,getBar
fooM(function(foo) {
getBar(foo ,next)
})
}
var bazM = function(next) {//闭包引用barM,getBaz
barM(function(bar) {
getBaz(bar ,next)
})
}
var snaM = function(next) {//闭包引用bazM,getSna
bazM(function(baz) {
getSna(baz ,next)
})
}
snaM(next)//闭包引用next
}
console.log("\ncall 8");
call8("o",function(param){console.log("end: "+param)});
9、规整化fooM
//根据barM,bazM,snaM用新增容器函数initM规整化fooM
function call9(param ,next) {
var initM = function(f) {//闭包引用param
f(param)
}
var fooM = function(next) {//闭包引用initM,getFoo
initM(function(arg) {
getFoo(arg ,next)
})
}
var barM = function(next) {//闭包引用fooM,getBar
fooM(function(foo) {
getBar(foo ,next)
})
}
var bazM = function(next) {//闭包引用barM,getBaz
barM(function(bar) {
getBaz(bar ,next)
})
}
var snaM = function(next) {//闭包引用bazM,getSna
bazM(function(baz) {
getSna(baz ,next)
})
}
snaM(next)//闭包引用next
}
console.log("\ncall 9");
call9("o",function(param){console.log("end: "+param)});
10、构造函数unit生成initM
//构造函数unit生成initM,initM闭包引用转为参数引用,隔离依赖,抽离调用
function call10(param ,next) {
function unit(arg) {
return function(f) {
f(arg);
}
}
var initM = unit(param);//闭包引用unit,param
var fooM = function(next) {//闭包引用initM,getFoo
initM(function(p) {
getFoo(p ,next)
})
}
var barM = function(next) {//闭包引用fooM,getBar
fooM(function(foo) {
getBar(foo ,next)
})
}
var bazM = function(next) {//闭包引用barM,getBaz
barM(function(bar) {
getBaz(bar ,next)
})
}
var snaM = function(next) {//闭包引用bazM,getSna
bazM(function(baz) {
getSna(baz ,next)
})
}
snaM(next);//闭包引用next
}
console.log("\ncall 10");
call10("o",function(param){console.log("end: "+param)});
11、构造函数bind生成fooM,barM,bazM,snaM
//构造函数bind生成fooM,barM,bazM,snaM,bind内闭包引用变成参数引用
function call11(param ,next) {
function unit(arg) {
return function(f) {
f(arg)
}
}
function bind(M ,f) {
return function(next) {
M(function(arg) {
f(arg ,next)
})
}
}
var initM = unit(param);//闭包引用unit,param
var fooM = bind(initM ,getFoo);//闭包引用initM,getFoo
var barM = bind(fooM ,getBar);//闭包引用fooM,getBar
var bazM = bind(barM ,getBaz);//闭包引用barM,getBaz
var snaM = bind(bazM ,getSna);//闭包引用bazM,getSna
snaM(next);//闭包引用next
}
console.log("\ncall 11");
call11("o",function(param){console.log("end: "+param)});
12、结合reduce简洁调用bind
//用Array.prototype.reduce使构造函数更简洁
function call12(param ,next) {
function unit(arg) {
return function(f) {
f(arg)
}
}
function bind(M ,f) {
return function(next) {
M(function(arg) {
f(arg ,next)
})
}
}
[getFoo ,getBar ,getBaz ,getSna].reduce(bind ,unit(param))(next)
}
console.log("\ncall 12");
call12("o",function(param){console.log("end: "+param)});
13、改造unit链式调用bind
//改造将bind合并为unit的方法,构成链式调用
function call13(param ,next) {
function unit(arg) {
var ret = function(f) {
f(arg)
}
function bind(f) {
var M = this//this代替M参数
,ret = function(next) {
M(function(arg) {
f(arg ,next)
})
}
;
ret.bind = bind;//.bind().bind
return ret
}
ret.bind = bind;//unit().bind
return ret
}
unit(param).bind(getFoo).bind(getBar).bind(getBaz).bind(getSna)(next)
}
console.log("\ncall 13");
call13("o",function(param){console.log("end: "+param)});
总结
闭包引用转参数引用
上面推导过程重复性很高,其中可以看到用了很多次闭包引用转参数引用,函数型语言处理副作用的方式很多,但多数要求隔离副作用,闭包引用使得对闭包环境的依赖深,转为参数引用可以将引用显式传入利于隔离。
规整化
函数型语言计算函数,抽离可重用部分,利用将未知函数转换为已知函数的思想,已知函数变得可重用,规整化可以将可重用部分规律化地显现出来,利于抽离可重用部分。
下一步
链式调用让我们联想到了promise,难道就是monad?这两者之间有什么关系,下回分解!