函数组合的定义
当我们需要依次通过多个函数进行处理,然后得到最终值,那么把这中间所有的函数组合成一个函数,这就叫函数组合。
例如现在有三个函数f1,f2,f3
,现在希望输入一个x依次调用这三个来得到y(注意,我们一般会将函数从右向左进行依次执行),过程大概是这样:y = f1(f2(f3(x)))
,即首先执行f3,将其返回值给f2,然后f2的返回结果给f1。这样形成一条完成的调用,这三个函数就像是一条条的管道一样,x会在管道里依次穿过,最终变成y。
那么此时我们就需要知道一点,那就是被组合的这些函数,必须是一个柯里化之后的纯函数,因为他们都接收一个参数,这个参数会经过每一个管道经过他们的处理,然后得到最终的结果。
不知道什么是“纯函数”和“柯里化”? 请看:函数式编程-纯函数和函数式编程-柯里化函数
如果将三个函数进行组合,那么大概会是这样:f = compose(f1,f2,f3)
。这个时候f
就是这三个函数的组合体,那么通过直接调用 f
就可以完成效果:y = f(x)
。
那么怎样落实到代码上呢?我们先来实现一个简单的版本:
function compose(f1,f2,f3){
return function(value){
return f1(f2(f3(value)))
}
}
现在我们来看一个需求:把 hi i am daniel wu 这句话变成 HI-I-AM-DANIEL-WU
其实这个需求直接用原生JS是很容易实现:
let str = "hi i am daniel wu";
str.toUpperCase().split(" ").join("-"); //HI-I-AM-DANIEL-WU
但是这样并不是我们要的“函数式编程”,因为这个需求并不复杂,所以在实际工作中这样写也没事儿,但是一旦项目大了就会乱作一团,久而久之就会有一种无力感。
那么我们怎么能使用上面的compose
函数来完成这个需求呢?我们可以借助于lodash这个库里的一些工具函数来进行操作。看需求 ,我们需要用到lodash中的toUpper
,split
,join
这些方法,但这三个方法当中,除了toUpper
之外,其它两个都需要传多个参数:
_.toUpper([string=''])
_.split([string=''], separator, [limit])
_.join(array, [separator=','])
这并不符合我们的要求,我们的要求是每一个要被组合的函数都是柯里化的函数,这样他才能只传一个input参数,所以我们需要对_.split()
和_.join()
进行柯里化:
const split = _.curry(function(sep,str){
return _.split(str,sep);
})
const join = _.curry(function(sep,arr){
return _.join(arr,sep);
})
这样,我们得到了两个柯理化之后的的函数,这个时候我们才能把它们进行组合:
//将 join, split , _.toUpper三个函数进行组合,他们从右向左依次执行,上一个执行的结果传给下一个函数。
const f = compose(join("-"),split(" "),_.toUpper);
const result = f("i am daniel wu");
console.log(result); //"I-AM-DANIEL-WU"
我们自己写的这个compose
函数功能太局限,只能接收三个函数,其实lodash当中已经为我们准备好了现在的组合函数的方法。
Lodash中的flow和flowRight
出于习惯,我们通常会使用flowRight
来进行合并函数,例如把之前的例子改一下:
const f = _.flowRight(join("-"),split(" "),_.toUpper);
const result = f("i am daniel wu");
console.log(result); //"I-AM-DANIEL-WU"
得到的结果和之前完全一样,但是我们使用lodash之后可以传N多个函数:
_flowRight(f1,f2,f3,f4...);
我们之前为了组合函数,把_.split
和_.join
又做了一次封闭,其实这完全是一个多余的操作,因为在lodash当中有更好的替代方案,根本不需要自己来写。
Lodash中的FP模块
FP模块中的工具方法全都是经过柯里化的,我们直接来看如何使用:
//引入lodash和lodash/fp模块
const _ = require('lodash');
const fp = require('lodash/fp');
const f = _.flowRight(fp.join("-"), fp.split(" "), _.toUpper);
const result = f("i am daniel wu");
console.log(result); //"I-AM-DANIEL-WU"
join
和split
全都换成了fp下面的方法,出来的结果也是正确的。