函数组合的定义

当我们需要依次通过多个函数进行处理,然后得到最终值,那么把这中间所有的函数组合成一个函数,这就叫函数组合。

例如现在有三个函数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"

joinsplit全都换成了fp下面的方法,出来的结果也是正确的。