函数式编程 -- 函数组合

文章内容输出来源:拉勾教育 大前端高薪训练营

前言

在学习函数组合之前,我们需要先了解管道的概念。管道在不同的领域,有不同的定义。那么在开发当中,管道是什么呢?

一、管道

当在程序中使用函数处理数据的时候,我们可以把所使用的函数看成是一个管道,通过函数输入对应的参数 ,可以返回相应的结果。

如图所示:
在这里插入图片描述

一个程序的运行当中,管道的数量是不固定的,有的时候甚至可以把一个大的管道分割成多个小的管道,然后通过组合的方式,得到程序运行后的结果。

如图所示:
在这里插入图片描述

简单来说,管道经常用来将某个命令或程序的输出提供给另一个命令或程序。

代码如下(示例):

	fn = compose(f1, f2, f3)
	b = fn(a)

二、函数组合(compose)

说完了日常的管道概念,下面我们来说一下函数组合。

1. 什么是函数组合

  • 如果一个函数要经过多个函数处理才能得到最终值,这个时候可以把中间过程的函数合并成一个函数。也就是说,将需要嵌套执行的函数进行平铺。嵌套执行指的是,一个函数的返回值将作为另一个函数的参数。
  • 函数就像是数据的管道,函数组合就是把这些管道连接起来,让数据穿过多个管道形成最终结果。
  • 函数组合默认是从右到左执行。

2. 模拟函数组合的实现过程

  • 一个简单的例子:

    代码如下(示例):

    // 函数组合演示
    function compose (f, g) { // f, g 都是函数,并且都作为参数进行传入
        return function (value) { // value 为输入的值
            return f(g(value)) // 先执行g函数,再执行f函数,符合函数组合默认从右到左执行
        }
    }
    // 箭头函数, 二选一, 此种方式更简便
    // let compose = (f, g) => value => f(g(value))
    
    let reverse = arr => arr.reverse()
    let first = arr => return array[0]
    
    // 先把数组进行反转,再进行取值
    const last = compose(first, reverse)
    /** -- 程序运行过程
     * last = (first, reverse) => value => first(reverse(value))
     */
    
    console.log(last([1, 2, 3, 4])); // 4
    

3. lodash 中的组合函数

lodash 中组合函数 flow() 或者 flowRight(),他们都可以组合多个函数。

  1. flow() 是从左到右运行。
  2. flowRight() 是从右到左运行,使用的更多一些。
  • 下面我们就来模拟一下flowRight()的实现过程:

    代码如下(示例):

    const _ = require('lodash');
    
    const reverse = arr => arr.reverse()
    const first = arr => arr[0]
    const toUpper = s => s.toUpperCase()
    
    // 多函数组合
    function compose(...args) { // 需要接收的参数不确定,使用剩余参数写法
         return function (value) { // 需要接收参数
             // 因为需要从右往左执行,所以需要对数组进行反转
             return args.reverse().reduce(function (acc, fn) {
                 return fn(acc)
             }, value)
         }
    }
     
    // 箭头函数写法
    const compose = (...args) => (value) => args.reverse().reduce((acc, fn) => fn(acc), value)
    /** -- 内部函数和reduce()执行过程
     *     args.reverse() = [reverse, first, toUpper]
     *     第一次迭代:acc = reverse, fn = first
     *     第二次迭代:acc = first(reverse), fn = toUpper
     *     第三次迭代:acc = toUpper(first(reverse)), 没有下一项
     *     因此 迭代结束,返回 toUpper(first(reverse))
     * 
     * ==> 最终结果
     * const f = function compose (toUpper ,first , reverse) {
     *    return function (value) {
     *          return toUpper(first(reverse))
     *    }
     * }
     * ==>
     * const = f = compose = (toUpper ,first , reverse) => value => toUpper(first(reverse))
     */
    const f = compose(toUpper ,first , reverse)
    
    console.log(f(['one', 'two', 'three'])) // THREE
    

reduce()具体用法,详情参见 数组(Array)的常用方法

  • 函数的组合要满足结合律 (associativity):
    我们既可以把 g 和 h 组合,还可以把 f 和 g 组合,结果都是一样的。

    代码如下(示例):

    // 结合律(associativity) 
    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 , 以上三种方式的结果相同
    

4. 调试

  • 如何调试组合函数:
    通过辅助函数记录日志,传入的参数为上一个函数的执行结果,然后观察每次返回的结果。

    代码如下(示例):

    // NEVER SAY DIE  --> never-say-die
    const _ = require('lodash')
    
    // tag 用作标记,v 表示实际传入的上一个函数的执行结果
    const trace = _.curry((tag, v) => {
        console.log(tag, v)
        return v
    })
    
    // 利用lodash中的curry()方法,将多个函数转换成单一函数,柯里化
    const split = _.curry((sep, str) => _.split(str, sep))	
    const join = _.curry((sep, array) => _.join(array, sep))
    
    // 对数组中的每一个元素进行处理,fn 决定如何去处理数组的每一项
    const map = _.curry((fn, array) => _.map(array, fn))
    
    // 会将split的结果返回给前一个函数
    const f = _.flowRight(join('-'), trace('map之后'), map(_.toLower), trace('split之后'), split(' '))
    
    console.log(f("NEVER SAY DIE")) // never-say-die
    

5. lodash中的fp模块

在lodash中文网,关于它里面的fp模块介绍,有这样一句话:

Load the FP build for immutable auto-curried iteratee-first data-last methods

简单来说,就是lodash中的fp模块提供了不可变的三个特性,即:

  • auto-curried(自动柯里化)

  • iteratee-first(函数优先)

  • data-last(数据滞后)

    代码如下(示例):

    // lodash 中的 fp 模块
    const fp = require('lodash/fp');
    
    const f = fp.flowRight(fp.join('-'), fp.map(fp.toLower), fp.split(' '))
    console.log(f("NEVER SAY DIE"));
    
  • lodash中方法 和 fp模块中方法 的不同
    1、lodash中方法: 未柯里化,数据优先,函数滞后
    2、fp模块中方法:已经被柯里化,函数优先,数据滞后

    下面我们通过代码的形式,lodash 和 lodash/fp 模块中 map 方法的区别:

    代码如下(示例):

    // const _ = require('lodash');
    
    // console.log(_.map(['23', '8', '10'], parseInt))
    // // parseInt('23', 0, array)
    // // parseInt('8', 1, array)
    // // parseInt('10', 2, array)
    
    const fp = require('lodash/fp')
    
    // 区别:接收参数不同
    console.log(fp.map(parseInt, ['23', '8', '10']))

6. Point Free

Point Free:我们可以把数据处理的过程定义成与数据无关的合成运算,不需要用到代表数据的那个参数,只要把简单的运算步骤合成到一起,在使用这种模式之前我们需要定义一些辅助的基本运算函数。

  • 不需要指明处理的数据

  • 只需要合成运算过程

  • 需要定义一些辅助的基本运算函数

    案例演示:

    // 非 Point Free 模式 
    // Hello World => hello_world 
    function f (word) { 
    	return word.toLowerCase().replace(/\s+/g, '_'); 
    }
    
    // Point Free 
    const fp = require('lodash/fp') 
    const f = fp.flowRight(fp.replace(/\s+/g, '_'), fp.toLower) 
    console.log(f('Hello World')) 
    
  • 使用 Point Free 的模式,把单词中的首字母提取并转换成大写

  • 代码如下(示例):

    const fp = require('lodash/fp') 
    const firstLetterToUpper = fp.flowRight(join('. '), fp.map(fp.flowRight(fp.first, fp.toUpper)), split(' ')) 
    
    console.log(firstLetterToUpper('world wild web')) // => W. W. W
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值