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

前言

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

一、管道

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

如图所示:

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

如图所示:

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

代码如下(示例):

 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
    

函数式编程 -- 函数组合相关推荐

  1. 翻译连载 | 附录 C:函数式编程函数库-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇...

    为什么80%的码农都做不了架构师?>>>    原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS> ...

  2. [一] java8 函数式编程入门 什么是函数式编程 函数接口概念 流和收集器基本概念...

    本文是针对于java8引入函数式编程概念以及stream流相关的一些简单介绍 什么是函数式编程?   java程序员第一反应可能会理解成类的成员方法一类的东西 此处并不是这个含义,更接近是数学上的函数 ...

  3. java中函数是什么_[一] java8 函数式编程入门 什么是函数式编程 函数接口概念 流和收集器基本概念...

    本文是针对于java8引入函数式编程概念以及stream流相关的一些简单介绍 什么是函数式编程? java程序员第一反应可能会理解成类的成员方法一类的东西 此处并不是这个含义,更接近是数学上的函数 看 ...

  4. 函数式编程 -- 函数是一等公民、高阶函数、闭包

    文章内容输出来源:拉勾教育 大前端高薪训练营 前言 学习函数式编程,首先要了解函数式编程相关的概念. 一.函数是一等公民 1. 一等公民的定义 根据维基百科,编程语言中一等公民的概念是由英国计算机学家 ...

  5. 函数式编程中的组合子

    函数式编程是一个比较大的话题,里面的知识体系非常的丰富,在这里我并不想讲的特别的详细.为了应对实际中的应用,我们讲一下函数式编程中最为实用的应用方式--组合子.组合子本身是一种高阶函数,他的特点就是将 ...

  6. 过程或函数的副作用是_Python函数和函数式编程(两万字长文警告!一文彻底搞定函数,建议收藏!)...

    Python函数和函数式编程 函数是可重用的程序代码段,在Python中有常用的内置函数,例如len().sum()等. 在Pyhon模块和程序中也可以自定义函数.使用函数可以提高编程效率. 1.函数 ...

  7. 【数据分析R语言系列】R语言函数与函数式编程、作用域和apply 家族

    文章目录 函数与函数式编程 创建和使用函数 作用域 任意参数 函数式编程 传入和返回函数 apply 家族 apply lapply.sapply 和 vapply 函数与函数式编程 函数是代码模板. ...

  8. Python进阶:函数式编程(高阶函数,map,reduce,filter,sorted,返回函数,匿名函数,偏函数)...啊啊啊...

    函数式编程 函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计.函数就是面向过程的程序设计 ...

  9. python中使用函数的优点是什么_python函数式编程是什么?

    在以下的文章之中我们来了解一下什么是python中的函数式编程.了解一下python中函数式编程是什么意思,以及python编程函数能应用在什么地方. 函数式编程 函数是Python内建支持的一种封装 ...

最新文章

  1. ECMAScript6入门--Class对象
  2. python从入门到精通书-100G Python从入门到精通全套资料!
  3. Linux arp相关命令(地址解析协议)
  4. 基于SAML2.0的SAP云产品Identity Authentication过程介绍
  5. shu函数php,【函数分享】每日PHP函数分享(2021-3-3)
  6. VUE - get 、post 请求后端接口:get 、post 写法 (Axios 中文说明文档地址)
  7. 再谈用MFC实现文件拖放到编辑框
  8. 使用 Hyper-v 虚拟化域控制器
  9. 《你不知道的JavaScript》-- 精读(一)
  10. 12-17 学习记录
  11. 大数据安全分析的特征有哪些
  12. XMLHttpRequest的方法
  13. ubuntu下安装win7虚拟机总结
  14. 20191009每日一句
  15. CBR,VBR,ABR介绍
  16. python pipe_Python os.pipe()用法及代码示例
  17. 计算机网络教室财产登记表,固定资产清查登记表-资产管理.DOC
  18. 工程材料学习3——第二章 金属材料组织和性能的控制(2.1 纯金属的结晶 2.2 合金的结晶)
  19. Xshell7工具下载安装以及简单使用
  20. 只用200行Go代码写一个自己的区块链!

热门文章

  1. Hessian局部线性嵌入算法(HLLE)——matlab实现
  2. Linux init详解 (0,1,2,3,4,5,6)
  3. 真的没办法一心一意麽? php 文件操作
  4. Simulink中如何定义变量的初始值
  5. redis 集群环境部署
  6. LeetCode 461. Hamming Distance
  7. 泛型数组 c# 0104
  8. django-redis的使用,利用配置中的缓存绑定数据库,直接获取连接对象
  9. 设置centos上的redis可以被访问
  10. AngularJs 时间格式化处理