函数组合

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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349