函数组合什么时候出道的_接口函数

函数组合什么时候出道的_接口函数函数组合在函数式编程中被称为组合(composition),我们将了解组合的概念并学习大量的例子。然后创建自己的compose函数。 组合的概念是非常直观的,并不是函数式编程独有的,在我们生活中或者前端开发中处处可见。 比如我们现在流行的 SPA (单页面应用),都会有组件的概…

你知道的越多,你不知道的越多
点赞再看,手留余香,与有荣焉

引言

函数组合在函数式编程中被称为组合(composition),我们将了解组合的概念并学习大量的例子。然后创建自己的compose函数。

组合的概念是非常直观的,并不是函数式编程独有的,在我们生活中或者前端开发中处处可见。

比如我们现在流行的 SPA (单页面应用),都会有组件的概念,为什么要有组件的概念呢,因为它的目的就是想让你把一些通用的功能或者元素组合抽象成可重用的组件,就算不通用,你在构建一个复杂页面的时候也可以拆分成一个个具有简单功能的组件,然后再组合成你满足各种需求的页面。

其实函数组合也是类似,函数组合就是一种将已被分解的简单任务组合成复杂任务的过程。

什么是组合

先看一个在 Linux 系统中常用的命令 ps -ef | grep node

这个命令的用处是将系统中与 node 有关的进程显示出来,其中ps -ef是显示所有进程的全格式,grep node是过滤与node有关的内容,|是将左侧的函数的输出作为输入发送给右侧的函数。

这个例子可能微不足道,但它传达了这样一个理念:

每一个程序的输出可以是另一个尚未可知的程序的输入

按照我们对组合的理解,现假定有compose函数可以实现如下功能:

function compose(...fns){
    //忽略
}
// compose(f,g)(x) === f(g(x))
// compose(f,g,m)(x) === f(g(m(x)))
// compose(f,g,m)(x) === f(g(m(x)))
// compose(f,g,m,n)(x) === f(g(m(n(x))))
//···

代码100分

我们可以看到compose函数,会接收若干个函数作为参数,每个函数执行后的输出作为下一个函数的输出,直至最后一个函数的输出作为最终的结果。

应用 compose 函数

在创建并完善我们自己的compose函数前,我们先来学习一下如何应用compose函数。

假定有这样一个需求:对一个给定的数字四舍五入求值,数字为字符型。

常规实现:

代码100分let n = '3.56';
let data = parseFloat(n);
let result = Math.round(data); // =>4 最终结果

在这段代码中,可以看到parseFloat函数的输出作为输入传递给Math.round函数以获得最终结果,这是compose函数能够解决的典型问题。

compose函数改写:

let n = '3.56';
let number = compose(Math.round,parseFloat);
let result = number(n); // =>4 最终结果

这段代码的核心是通过composeparseFloatMath.round组合到一起,返回一个新函数number

这个组合的过程就是函数式组合!我们将两个函数组合在一起以便能及时的构造出一个新函数!

再举一个例子,假设我们有两个函数:

代码100分let splitIntoSpaces = str => str.split(' ');
let count = array => array.length;

现希望构建一个新函数以便计算一个字符串中单词的数量,可以很容易的实现:

let countWords = compose(count,splitIntoSpaces);

调用一下:

let result = countWords('hello your reading about composition'); // => 5

开发中组合的用处

假设我们有这样一个需求:给你一个字符串,将这个字符串转化成大写,然后逆序。

我们的常规思路如下:

let str = 'jspool'

//先转成大写,然后逆序
function fn(str) {
    let upperStr = str.toUpperCase()
    return upperStr.split('').reverse().join('')
}

fn(str) // => "LOOPSJ"

这段代码实现起来没什么问题,但现在更改了需求,需要在将字符串大写之后,将每个字符拆开并封装成一个数组:

“jspool” => ["J","S","P","O","O","L"]

为了实现这个目标,我们需要更改我们之前封装的函数,这其实就破坏了设计模式中的开闭原则。

开闭原则:软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的。

那么在需求未变更,依然是字符串大写并逆序,应用组合的思想来怎么写呢?

原需求,我们可以这样实现:

let str = 'jspool'

function stringToUpper(str) {
    return str.toUpperCase()
}

function stringReverse(str) {
    return str.split('').reverse().join('')
}

let toUpperAndReverse = compose(stringReverse, stringToUpper)
let result = toUpperAndReverse(str) // "LOOPSJ"

那么当我们需求变化为字符串大写并拆分为数组时,我们根本不需要修改之前封装过的函数:

let str = 'jspool'

function stringToUpper(str) {
    return str.toUpperCase()
}

function stringReverse(str) {
    return str.split('').reverse().join('')
}

function stringToArray(str) {
    return str.split('')
}

let toUpperAndArray = compose(stringToArray, stringToUpper)
let result = toUpperAndArray(str) // => ["J","S","P","O","O","L"]

可以看到当变更需求的时候,我们没有打破以前封装的代码,只是新增了函数功能,然后把函数进行重新组合。

可能有人会有疑问,应用组合的方式书写代码,当需求变更时,依然也修改了代码,不是也算破坏了开闭原则么?其实我们修改的是调用的逻辑代码,并没有修改封装、抽象出来的代码,而这种书写方式也正是开闭原则所提倡的。

我们假设,现在又修改了需求,现在的需求是,将字符串转换为大写之后,截取前3个字符,然后转换为数组,那么我们可以这样实现:

let str = 'jspool'

function stringToUpper(str) {
    return str.toUpperCase()
}

function stringReverse(str) {
    return str.split('').reverse().join('')
}

function getThreeCharacters(str){
    return str.substring(0,3)
}

function stringToArray(str) {
    return str.split('')
}

let toUpperAndGetThreeAndArray = compose(stringToArray, getThreeCharacters,stringToUpper)
let result = toUpperAndGetThreeAndArray(str) // => ["J","S","P"]

从这个例子,我们可以知道,组合的方式是真的就是抽象单一功能的函数,然后再组成复杂功能,不仅代码逻辑更加清晰,也给维护带来巨大的方便。

实现组合

先回看compose函数到底做了什么事:

// compose(f,g)(x) === f(g(x))
// compose(f,g,m)(x) === f(g(m(x)))
// compose(f,g,m)(x) === f(g(m(x)))
// compose(f,g,m,n)(x) === f(g(m(n(x))))
//···

概括来说,就是接收若干个函数作为参数,返回一个新函数。新函数执行时,按照由右向左的顺序依次执行传入compose中的函数,每个函数的执行结果作为为下一个函数的输入,直至最后一个函数的输出作为最终的输出结果。

如果compose函数接收的函数数量是固定的,那么实现起来很简单也很好理解。

只接收两个参数:

function compose(f,g){
    return function(x){
        return f(g(x));
    }
}

只接收三个参数:

function compose(f,g,m){
    return function(x){
        return f(g(m(x)));
    }
}

上面的代码,没什么问题,但是我们要考虑的是compose接收的参数个数是不确定的,我们考虑用rest参数来接收:

function compose(...fns){
    return function(x){
        //···
    }
}

现在compose接收的参数fns是一个数组,那么现在思考的问题变成了,如何将数组中的函数从右至左依次执行。

我们选择数组的reduceRight函数来实现:

function compose(...fns){
    return function(x){
        return fns.reduceRight(function(arg,fn){
            return fn(arg);
        },x)
    }
}

这样我们就实现了compose函数~

实现管道

compose的数据流是从右至左的,因为最右侧的函数首先执行,最左侧的函数最后执行!

但有些人喜欢从左至右的执行方式,即最左侧的函数首先执行,最右侧的函数最后执行!

从左至右处理数据流的过程称之为管道(pipeline)!

管道(pipeline)的实现同compose的实现方式很类似,因为二者的区别仅仅是数据流的方向不同而已。

对比compose函数的实现,仅需将reduceRight替换为reduce即可:

function pipe(...fns){
    return function(x){
        return fns.reduce(function(arg,fn){
            return fn(arg);
        },x)
    }
}

组合相比,有些人更喜欢管道。这只是个人偏好,与底层实现无关。重点是pipecompose做同样的是事情,只是数据流放行不同而已!我们可以在代码中使用pipecompose,但不要同时使用,因为这会在团队成员中引起混淆。如果要使用,请坚持只用一种组合的风格。

系列文章推荐

  • 「前端进阶」单页路由解析与实现
  • 「前端进阶」彻底弄懂函数柯里化
  • 「前端进阶」JS中的栈内存堆内存
  • 「前端进阶」JS中的内存管理
  • 「前端进阶」数组乱序

参考

  • JavaScript ES6函数式编程入门经典

写在最后

  • 文中如有错误,欢迎在评论区指正,如果这篇文章帮到了你,欢迎点赞关注
  • 本文同步首发与github,可在github中找到更多精品文章,欢迎Watch & Star ★
  • 后续文章参见:计划

欢迎关注微信公众号【前端小黑屋】,每周1-3篇精品优质文章推送,助你走上进阶之旅

函数组合什么时候出道的_接口函数

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
转载请注明出处: https://daima100.com/11333.html

(0)
上一篇 2023-03-02
下一篇 2023-03-02

相关推荐

  • 02、对数据库的操作[亲测有效]

    02、对数据库的操作[亲测有效]数据库的操作不管是在Windows系统还是Linux系统的是一样的。小编在此先以Windows系统下安装的数据库为例,Linux系统下如何安装及其使用,在后面的大数据中会讲。 1、创建数据库 CREA

    2023-02-08
    149
  • redis怎么持久化数据

    redis怎么持久化数据Redis为了持久化数据提供了两种方式:RDB方式和AOF方式。RDB方式的持久化是通过快照(snapshotting)完成的,当符合一定条件时,Redis会自动将内存中所有的数据生成一份副本并存储…

    2022-12-20
    146
  • mysql死锁例子_数据库死锁

    mysql死锁例子_数据库死锁最近项目中某个模块稳定复现MySQL死锁问题,本文记录死锁的发生原因以及解决办法。 1. 预备知识 1.1 表锁和行锁 表锁 表锁是MySQL中最基本的锁策略,并且是开销最小的策略。表锁会锁定整张数据

    2023-04-15
    149
  • redis图形化界面_redis operator

    redis图形化界面_redis operator1.介绍 使用redis-cli客户端工具,必须使用命令进行操作,效率比较低。 将使用图形化工具对redis数据库中的数据进行管理。0.9.4之后开始收费 redis-desktop-manager…

    2023-02-22
    180
  • 高性能MySQL(第4版) 第一章 MySQL架构 读书笔记[通俗易懂]

    高性能MySQL(第4版) 第一章 MySQL架构 读书笔记[通俗易懂]这本书去年11月出的,今年中文版也出了,并且直接上了微信读书,之后有空就读一读,分享下读书笔记~ 原文内容比较充实,建议有时间可以读一下原文. 第一章主要是个概览. MySQL的逻辑架构 默认情况下,

    2023-06-14
    152
  • 用Python调用函数

    用Python调用函数在Python中,函数是一组代码段,用于执行特定的任务。函数可以接受参数并返回值。在Python程序中,如果有一个函数可以完成我们需要的任务,我们可以在程序中调用它。调用函数的语法如下:

    2024-06-28
    52
  • 用Python处理JSON文件

    用Python处理JSON文件JSON是一种轻量级的数据交换格式,广泛用于Web应用中的数据传输。Python是一种强大的编程语言,支持对JSON格式数据的处理和解析。本文将从多个方面详细解释如何用Python处理JSON文件。

    2024-09-07
    24
  • 初识Hadoop的三种安装模式「终于解决」

    初识Hadoop的三种安装模式「终于解决」特点:高可靠性(不怕丢)、高效性(处理速度快)、高容错性 ps:使用Hadoop版本: 接下来所用到的Hadoop2.8.5,虽然目前Hadoop已经更新到3.x了;但是我们始终秉持一个观点“用旧不用

    2023-04-19
    151

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注