函数组合

1. 函数组合可以让我们把细粒度的函数重新组合生成一个新的函数

前面两个文章所说的纯函数和柯里化很容易写出洋葱代码  h(g(f(x)))

接下来,我们看一下lodash中的函数组合以及函数组合是如何实现的?

2. lodash中的函数组合

  1. 函数组合 (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);
    /*
     获取数组的最后一个值,看到这里,可能有人会问,我直接去取数组中的最后一个值不就好了吗
     为什么还要写两个函数?
     这里我们就可以说下函数组合的作用了: 函数组合是将多个基本函数合并成一个你需要的函数
     这些基本函数可能我们以后会在某些需求中还用得到,而你直接取最后一个值你只能够应付这一个需求
    */
    
  2. 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. 模拟实现 lodashflowRight方法

// 多函数组合
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)

  1. 我们既可以把 gh 组合,还可以把 fg 组合,结果都是一样的

    // 结合律(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

  1. lodashfp 模块提供了实用的对函数式编程友好的方法
  2. 提供了不可变 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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。