1. 函数组合可以让我们把细粒度的函数重新组合生成一个新的函数
前面两个文章所说的纯函数和柯里化很容易写出洋葱代码 h(g(f(x)))
接下来,我们看一下lodash中的函数组合以及函数组合是如何实现的?
2. lodash
中的函数组合
-
函数组合 (
compose
):如果一个函数要经过多个函数处理才能得到最终值,这个时候可以把中间过程的函数合并成一个函数- 函数就像是数据的管道,函数组合就是把这些管道连接起来,让数据穿过多个管道形成最终结果
- 函数组合默认是从右到左执行
// 组合函数 function compose(f, g) { // f 和 g 是两个函数参数 return function (x) { return f(g(x)); } } function first(arr) { return arr[0]; } function reverse(arr) { return arr.reverse(); } // 从右到左运行 let last = compose(first, reverse); /* 获取数组的最后一个值,看到这里,可能有人会问,我直接去取数组中的最后一个值不就好了吗 为什么还要写两个函数? 这里我们就可以说下函数组合的作用了: 函数组合是将多个基本函数合并成一个你需要的函数 这些基本函数可能我们以后会在某些需求中还用得到,而你直接取最后一个值你只能够应付这一个需求 */
-
lodash
中组合函数flow()
或者flowRight()
,他们都可以组合多个函数-
flow()
是从左到右运行 -
flowRight()
是从右到左运行,使用的更多一些
const _ = require('lodash'); const toUpper = s => s.toUpperCase(); // 将字符串转换为大写的基本函数 const reverse = arr => arr.reverse(); // 将数组反转的基本函数 const first = arr => arr[0]; // 查找数组中第一个元素的基本函数 // 组合起来就是 查找一个数组中的最后一个元素,并将其转换为大写 const f = _.flowRight(toUpper, first, reverse); console.log(f(['one', 'two', 'three']));
-
3. 模拟实现 lodash
的 flowRight
方法
// 多函数组合
function compose(...fns) {
// 返回一个函数,因为还需要接收参数
return function (value) {
/*
因为flowRight方法是从右往左依次执行函数,所以我们得把所有函数参数的位置通过数组的reverse()反转过来
然后需要将当前函数执行结果的返回值传给下一个函数当作参数,所以我们可以调用数组的reduce方法
最后需要返回 fn(acc) 是因为如果你不return,它默认返回的是undefined,而我们下个函数需要的参数是当前函数执行的返回结果
*/
return fns.reverse().reduce(function (acc, fn) {
return fn(acc);
}, value);
}
}
// ES6
const compose = (...fns) => value => fns.reverse().reduce((acc, fn) => fn(acc), value);
4. 函数组合要满足结合律 (associativity
)
-
我们既可以把
g
和h
组合,还可以把f
和g
组合,结果都是一样的// 结合律(associativity) let f = compose(f, g, h); let associative = compose(compose(f, g), h) == compose(f, compose(g, h)) // true
所以代码还可以像下面这样
const _ = require('lodash'); const f = _.flowRight(_.toUpper, _.first, _.reverse) // ==> 等价于 // const f = _.flowRight(_.flowRight(_.toUpper, _.first), _.reverse) // ==> 等价于 // const f = _.flowRight(_.toUpper, _.flowRight(_.first, _.reverse)) console.log(f(['one', 'two', 'three'])); // => THREE
5. 函数组合调试
const _ = require('lodash');
/*
因为 _.split _.join _.map 所接收的参数和我们想要传的参数顺序不一致
所以我们要使用柯里化 对三个函数进行重构 让我们把想传入的值变为最后一个传入
有人会说:这也太麻烦了 我以后碰到一些数值是第一个参数的函数那不是都得重写
别急~~ 接下来我会介绍简单方法
*/
const split = _.curry((sep, str) => _.split(str, sep));
const join = _.curry((sep, array) => _.join(array, sep));
const map = _.curry((fn, array) => _.map(array, fn));
/*
起到一个中间件的作用,便于代码调试
一旦出错,我们可以快速的定位到是那块出了问题
tag: 上个调用的函数名
v: 上个函数所返回的结果
*/
const trace = _.curry((tag, v) => {
console.log(tag, v);
return v;
});
// 将一个字符串转换为小写并用 - 连接
const f = _.flowRight(join('-'), trace('map 之后'), map(_.toLower), trace('split 之后'), split(' '));
console.log(f('NEVER SAY DIE'));
/*
=> split 之后 [ 'NEVER', 'SAY', 'DIE' ]
=> map 之后 [ 'never', 'say', 'die' ]
=> never-say-die
*/
6. lodash/fp
-
lodash
的fp
模块提供了实用的对函数式编程友好的方法 - 提供了不可变
auto-curried iteratee-first data-last
的方法
// 上面同样的例子 用 fp就可以这样写
const fp = require('lodash/fp');
const f = fp.flowRight(fp.join('-'), fp.map(_.toLower), fp.split(' '))
console.log(f('NEVER SAY DIE')); // => never-say-die