大家是不是看我上篇博文有点蒙。用的的curry和compose是什么鬼,怎么那么神奇。上篇博文也是主要用到了这两个函数。
那今天我们来聊一下curry和compose,这两个东西是函数式编程很重要的东西,基本每个稍微复杂点的例子都要涉及这两个函数。
什么是curry呢?
---函数柯里化。就是这个东西了。举一个简单的例子。
var _console=curry(function(x){ console.log(x); }) _console("hello"); //hello
其实就这个作用,先定义函数,后传参数。_console保存了这个函数,每次调用这个函数的时候传给它参数。是不是很简单呢?
看一下curry的实现。
function curry(fn){ return function(f){ return fn(f); } }
只是返回一个函数那么简单,这里还涉及fn的控制权反转,类似redux的dispatch,传入dispatch使用它。
那么当这个curry函数有2个参数的时候,curry就得变为:
function curry(fn){ return function(f){ return function(g){ return fn(f,g); } } }
那么有3个参数呢?
function curry(fn){ return function(f){ return function(g){ return function(h){ return fn(f,g,h); } } } }
那么当我想一次传2个参数,剩下一个传1个参数怎么办?
function curry(fn){ return function(f){ return function(g,h){ return fn(f,g,h); } } }
那么我改主意了,我不确定每次传多少个参数了,我不确定一共传多少个参数了。怎么办?
自动柯里化函数,你会发现_.curry完美的解决了这个问题,甚至可以在你传一个空参数的时候忽略它。
今天我们不研究_.curry的实现,有大神可以分享一下~
我们今天写一个自动柯里化函数。
怎么写?什么思路呢?它的难点在哪?
首先我们需要知道自动柯里化函数的执行过程和普通柯里化函数的执行过程是一样的,即有多少参数返回多少的function,取到函数的参数的长我们知道,
但是函数又不是数组,可以用一层for循环return出来,只能改变思路,函数的循环就是递归了。
它的难点在哪?
我们需要在每次执行一次()的时候获取他的参数,在递归里我们可以声明一个外部变量而储存每次执行的参数。
但是这个是不行的,因为每次递归都需要返回一个function,这意味着你无法返回上一层了。那我们只能把执行的参数当函数的参数传进去了。
直接把自动柯里化给你们(复制可测试)
function curry (fn, length, args) { length = length || fn.length; //保存函数的参数数量 args = args || []; return function(){ var _args = args.slice(0), arg, i; for(i = 0; i < arguments.length; i++) {//遍历_args,存储每次执行的参数 arg = arguments[i]; _args.push(arg); } if(_args.length < length) { return curry.call(this, fn, length, _args); //递归调用自己 } else { return fn.apply(this, _args); } } }
可以看到解决return函数个数的是
if(_args.length < length) { return curry.call(this, fn, length, _args); }else { return fn.apply(this, _args); }
_arg储存了从开始到现在的所有的参数,如果参数”够了“(一个小知识,函数的length==函数参数的个数),就调用fn,把所有参数都传给他,
如果不够,就继续递归,递归的时候呢,把当前的参数传到下一层函数里。
难点就在于每次递归回来都要遍历一下当前的参数,把它push到_args里面。
这样做也避免了空参数的情况,当传入一个空参数,比如这样调用()。_args不变。
大家有空可以研究一下_.curry的实现过程是不是类似呢?-0-
下一个主角,compose,这个函数在fp里面叫组合函数,它能把里面的参数都组合起来,一起调用。类似流式。
function compose(fn1,fn2){ return function(arg){ fn2(fn1(arg)); } } var gen= compose(function(b){console.log(b);return b+"be deal with b";} , function(a){console.log(a);return a+"be deal with a";} ) gen("holle"); //hello hello deal with b;
gen就像一个水管,gen("holle");就是往水管里注入了水。
它会从compose左边的函数开始执行,hello是数据流,流过2个函数。第一个函数的返回值会传入到第二个参数继续执行。
在函数式编程里,compose是避免命令式的重要一环,fp要做到的每一个函数都是”纯“的,脏的东西交给使用者。把每个纯的函数组合到compose里面,就像gulp的pipe()一样,每个函数就像一个插件,来处理数据流。
那么我想从右到左处理数据流,
function compose(fn1,fn2){ return function(arg){ fn1(fn2(arg)); } }
那么和curry一样,我不想只能传2个函数,我想传任意函数怎么办?
这里我们不写自动组合函数了,因为在我分析redux源码的时候发现了这个函数,redux帮我们写了~我们就只来分析一下就好了。
先上一下源码。redux不跟lodash一样一个函数跳来跳去,lodash想只看一部分是不好懂的。redux的compose函数(复制可测试)。
function compose() { for (var _len = arguments.length, funcs = Array(_len), _key = 0; _key < _len; _key++) { funcs[_key] = arguments[_key]; //把参数都复制到funcs数组里。 } return function () { if (funcs.length === 0) { return arguments.length <= 0 ? undefined : arguments[0]; //处理无参数的情况,返回undefined } var last = funcs[funcs.length - 1]; //这是最后一个处理函数 var rest = funcs.slice(0, -1); //取除了最后一个剩下的处理函数 return rest.reduceRight(function (composed, f) {//每次都执行当前处理函数传入基数 return f(composed); }, last.apply(undefined, arguments)); //执行最后一个处理函数,返回值作为reduce的基数。 }; }
他的原理很简单,就是把arguments的所有函数都执行一次,把上个函数的返回值传入。这里巧妙的用了数组的reduceRight函数。
你换成reduce也可以,那这个last要换成first,这样就是从左到右执行啦。处理顺序的问题。
它俩是很有用的,例子可以看上篇博文~~点我。
学好这两个函数,我们再打开函数式编程的大门吧~