背景
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?这两者之间有什么关系,下回分解!
参考
-
- deriving a useful monad in javascript
- 你造Promise 就是 Monad 吗
- monads in javascript
- Functor, Applicative, 以及 Monad 的图片阐释(此为中午翻译,原网站已经失效,GitHub:https://github.com/jiyinyiyong),英文原始链接:http://adit.io/posts/2013-04-17-functors%2C_applicatives%2C_and_monads_in_pictures.html
原文标题:函数式JS: 一种continuation monad推导
原文链接:http://hai.li/2017/03/08/css-multiline-overflow-ellipsis.html
原文快照:无
文章评论