目录:
2、pipe 函数
compose 函数将需要嵌套执行的函数平铺。嵌套执行指的是,一个函数的返回值作为另一个函数的参数。
其实 compose 函数在函数式编程中通常用来将几个有特点的函数拼凑在一起,让他们结合,产生一个崭新的函数。compose 函数的作用是实现函数式编程的 Pointfree, 使我们专注于转换而不是数据。也就是说,我们完全可以把数据处理的过程定义成一种与参数无关的合成运算,不需要用到代表数据的那个参数,只要把一些简单的运算步骤合成在一起即可,而 Pointfree 指的是不使用所要处理的值,只合成运算过程,即我们所指的无参数分隔。
现在有这么一个需求,计算一个数,加上 10,再乘以 10的值。可能有人会这么做:
1 let calculate = x => ( x + 10 ) *10; 2 console.log( calculate( 10 ) ); //输出:200
上面,这样写是没有问题的,但是这是命令式编程,这样的代码完全不具备复用性。作为一个函数式开发者,会对这段代码进行抽象来实现简化代码的目的,而且,函数式编程就是将程序分解为一些更可重用、更可靠、更易于理解的部分,然后再将它们组合起来形成一个更易于推理的程序整体。
以上面的需求为例,我们关注的是它的动作,先加上 10, 再乘以 10。可以像下面这样做:
1 let add = x => x + 10; 2 let multiply = y => y * 10; 3 console.log( multiply(add(10)) ); //输出:200
上面,将 add 函数的执行结果作为 multiply 的参数,这样也能实现需求。但是根据复合函数的定义,需要将代表两个动作的函数合成一个函数。分析 2 个函数的特性,第一,是两个函数都有一个共同的参数,第二,是函数的执行的顺序是从右到左,第三,是前面函数的结果将由后面的函数处理,根据特性,可以这样实现:
1 let add = x => x + 10; 2 let multiply = y => y * 10; 3 let compose = (f, g) => { 4 return function(x){ 5 return f(g(x)); 6 } 7 } 8 let calculate = compose( multiply, add ); 9 console.log( calculate(10) ); //输出:200
上面,使用了闭包来实现。首先,定义一个 compose 函数,接收 2 个函数作为参数。这个 compose 函数返回一个匿名函数,这个匿名函数接收 1 个参数,而且这个匿名函数将 2 个函数执行的结果返回。当调用的时候,只需要将 2 个函数作为参数传入。注意,根据函数调用的顺序,这里的执行动作是从右往左,即先执行 add ,再执行 multiply。
这样的 compose 函数没有通用性,因为它只可以处理 2 个函数,接下来实现通用的 compose 函数:
1 let add = x => x + 10; 2 let multiply = y => y * 10; 3 let compose = function(){ 4 let args = [].slice.call( arguments ); 5 return function( x ){ 6 return args.reduceRight( function (res, cb) { 7 return cb(res); 8 }, x) 9 } 10 } 11 let calculate = compose( multiply, add ); 12 console.log( calculate(10) ); //输出:200
上面,通用的 compose 函数要做的第一件事是将传入的函数作为参数收集起来,所以这里将 arguments 转换为数组,因为需要把上一次执行结果当作参数传给下一次执行,所以可以使用 reduce() 来实现,reduce方法可以接收一个函数作为累加器,也就是说 reduce() 会将数组中的每一个元素依次执行传入的函数。同时要注意,复合函数执行的顺序是自右向左依次执行,而 reduce() 是从左往右开始累加,reduceRight() 是从右往左开始累加,所以这里用 reduceRight()。调用 compose 函数的执行顺序依然是从右往左执行。
可以使用更简洁的 es6 语法实现 compose 函数,如下:
1 let add = x => x + 10; 2 let multiply = y => y * 10; 3 //es6 写法 4 const compose = (...args) => x => args.reduceRight( (res, cb ) => cb(res), x); 5 let calculate = compose( multiply, add ); 6 console.log( calculate(10) ); //输出:200
其实原理都是将传入的函数收集,唯一要注意的是:compose 执行方向是从右往左执行,所以要使用 reduceRight() 。通过 compose,在开发中可以去设计和抽象功能到具体的函数里面,以便后期的复用。而且更多的时候,compose 函数的存在其实是服务中间件的,redux 中间件就是通过 compose 来实现的。webpack loader 的加载顺序也是从右往左的,这是因为 webpack 选择了 compose 方式去实现。
compose 和 pipe 都是函数组合,组合是函数式编程非常重要的思想,其实,就是将多个函数组合在一起,以便能够构建出一个新函数。在函数式编程中,纯函数应该被设计为只做一件事,如果想实现多个功能,可以通过函数的组合来实现。
pipe 函数是 compose 函数的复制品,唯一修改的是数据流方向,pipe 函数和 compose 函数做的事情相同,只不过交换了数据流方向,也就是说,compose 函数数据流的方向是从右向左,最右侧的函数会首先执行,之后数据传递给下一个函数,以此类推,一直到最左侧的函数最后执行。而 pipe 函数正好相反。