函数式编程 - 组合compose的使用方法

news2024/9/23 11:13:30

函数式编程中有一个比较重要的概念就是函数组合(compose),组合多个函数,同时返回一个新的函数。调用时,组合函数按顺序从右向左执行。右边函数调用后,返回的结果,作为左边函数的参数传入,严格保证了执行顺序,这也是compose 主要特色。| 函数式编程--函数组合(Function composition) - 知乎

入门简介


组合两个函数

compose 非常简单,通过下面的示例代码,就非常清楚。

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

var arr = [1, 2, 3],
reverse = function(x){ return x.reverse()},
getFirst = function(x) {return x[0]},
compseFunc = compose(getFirst, reverse);
    
compseFunc(arr);   // 3

参数在函数间就好像经过‘管道’传输同样,最右边的函数接收外界参数,返回结果传给左边的函数,最后输出结果。

组合任意个函数

上面组合了两个函数的compose,也让咱们了解了组合的特点,接着咱们看看如何组合更多的函数,由于在实际应用中,不会像入门介绍的代码那么简单。

主要注意几个关键点:

1、利用arguments的长度得到所有组合函数的个数

2、reduce 遍历执行全部函数。

var compose = function() {
      var args = Array.prototype.slice.call(arguments);
      
      return function(x) {
       if (args.length >= 2) {
       
          return args.reverse().reduce((p, c) => {
            return p = c(p)
         }, x)
         
       } else {
           return args[1] && args[1](x);
       }
      }
    }
   
// 利用上面示例 测试一下。
var arr = [1, 2, 3],
reverse = function(x){ return x.reverse()},
getFirst = function(x) {return x[0]},
trace = function(x) {  console.log('执行结果:', x); return x}
    
compseFunc = compose(trace, getFirst, trace, reverse);
    
compseFunc(arr);   
 // 执行结果: (3) [3, 2, 1]
 // 执行结果: 3
 // 3

如此实现,基本没什么问题,变量arr 在管道中传入后,经过各种操作,最后返回了结果。

深入理解


认识pipe

函数式编程(FP)里面跟compose类似的方法,就是pipe

pipe,主要作用也是组合多个函数,称之为, 肯定得按照正常方法,从左往右调用函数,与compose 调用方法相反。

ES6 实现Compose function

先看下compose 最基础的两参数版本

const compose = (f1, f2) => value => f1(f2(value));

利用箭头函数,非常直接的表明两个函数嵌套执行的关系,接着看多层嵌套。

(f1, f2, f3...) => value => f1(f2(f3));

抽象出来表示:

() => () => result;

先提出这些基础的组合方式,对我们后面理解高级ES6方法实现compose有很大帮助。

实现pipe

前面提到 pipe 是反向的compose,pipe正向调用也致使它实现起来更容易。

pipe = (...fns) => x => fns.reduce((v, f) => f(v), x)

一行代码就实现了pipe,套用上面抽象出来的表达式,reduce恰好正向遍历全部函数, 参数x做为传递给函数的初始值, 后面每次f(v)执行的结果,作为下一次f(v)调用的参数v,完成了函数组合调用。

或者,能够把函数组合中,第一个函数获取参数后,获得的结果,作为reduce遍历的初始值。

pipe = (fn,...fns) => (x) => fns.reduce( (v, f) => f(v), fn(x));

利用ES6提供的rest 参数 ,用于获取函数的多余参数,提取出第一个函数fn,多余函数参数放到fns中,fns看成是数组,也不用像arguments那种事先经过Array.prototype.slice.call转为数组,arguments对性能损耗也可以避免。 fn(x) 第一个函数执行结果做为reduce 初始值。

注:关于剩余参数rest,可参考我之前文章:【JS高级】ES6参数增强之剩余参数的应用

实现compose

1、pipe 部分,利用reduce实现,反过来看,compose就能够利用reduceRight

compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);

2、利用递归

compose = (fn, ...fns) => fns.length === 0 ? fn: (...args) => fn(compose(...fns)(...args))

递归代码,首先看出口条件, fns.length === 0,最后一定执行最左边的函数,然后把剩下的函数再经过compose调用,

3、利用reduce实现。

一行实现,并且仍是用正向的 reduce

const compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args)))

作者其实用例子作了解释,可以看下reduce 迭代的方向是从左往右的,而compose 要求执行的方向是从右往左。对数组中每一项执行函数,正常状况下都应该放回执行结果,比如(v, f) => f(v),返回f(v)执行结果,这里是(f, g) => (...args) => f(g(...args))返回一个函数(...args) => f(g(...args)),这样就能够保证后面的函数g在被作为参数传入时比前面的函数f先执行。

简单利用前面的组合两个函数的例子分析一下。

...
composeFunc = compose(getFirst, trace, reverse);
composeFunc(arr);

主要看reduce 函数里面的执行过程:

◆ 入口 composeFunc(arr), 第一次迭代,reduce函数执行 (getFirst, trace) => (...args)=>getFirst(trace(...args)),函数(...args)=>getFirst(trace(...args))作为下一次迭代中累计器f的值。

◆ 第二次迭代,reduce函数中

f == (...args)=>getFirst(trace(...args))
g == reverse。
// 替换一下 (f, g) => (...args) => f(g(...args))
((...args)=>getFirst(trace(...args)), reverse) => (...args) => ((...args)=>getFirst(trace(...args)))(reverse(...args))

◆ 迭代结束,最后得到的comoseFunc就是

// 对照第二次的执行结果, (...args) => f(g(...args))

(...args) => ((...args)=>getFirst(trace(...args)))(reverse(...args))

◆ 调用函数composeFunc(arr)。

(arr) => ((...args)=>getFirst(trace(...args)))(reverse(arr))

===》reverse(arr) 执行结果[3, 2, 1] 做为参数

 ((...args)=>getFirst(trace(...args)))([3,2,1])

==》入参调用函数

   getFirst(trace[3,2,1])

===》 

   getFirst([3, 2, 1])

===》

   结果为 3

非常巧妙的把后一个函数的执行结果作为包裹着前面函数的空函数的参数,传入执行。其中大量用到下面的结构。

((arg)=> f(arg))(arg) 
// 转换一下。
  (function(x) {
     return f(x)
  })(x)

最后

不管是compose, 仍是后面提到的pipe,概念很是简单,均可以使用很是巧妙的方式实现(大部分使用reduce),并且在编程中很大程度上简化代码。最后列出优秀框架中使用compose的示例:

  • redux/compose
  • koa-Compose
  • underscorejs/compose

参考连接

  • Creating an ES6ish Compose in Javascript
  • compose.js
  • Optimization-killers

参考资料:

JS高级编程中compose函数的介绍和基本实现 |  复合函数compose函数的概念

compose函数 | 函数式编程--函数组合(Function composition) | 函数式编程之compose

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1380964.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【高等数学之不定积分】

一、什么是不定积分? 我们可以简单地从英文层面来基础剖析一下,什么是不定积分? 1.1、基本概念 小tips: 二、不定积分运算法则 三、常用积分公式 四、第一类换元积分法 4.1、定义 4.2、常用凑微分公式 4.3、小calculate 五、第二类换元积分法 5.1、定义 …

【已解决】C语言实现多线程的同步与异步

说真的写了这篇博文时,才知道c语言本身不支持多线程,而是一些windowsapi让c语言拥有多线程的能力,那下面内容就以打开对话框为例,展现如何实现多线程的同步与异步。 文章目录 问题起源c语言多线程同步方案c语言多线程异步方案总结…

JOSEF约瑟端子排中间继电器 DZY-204 DC110V 导轨安装,板前接线

DZY系列端子排中间继电器 系列型号: DZY-101端子排中间继电器 DZY-104端子排中间继电器 DZY-105端子排中间继电器 DZY-301端子排中间继电器 DZY-106端子排中间继电器 DZY-401端子排中间继电器 DZY-204端子排中间继电器 一、 概述 DZY-204端子排中间继电器用于各种…

【金猿CIO展】步长制药信息化管理与建设中心总经理束炼:IT部门既要懂技术,也要懂业务...

‍ 束炼 本文由步长制药信息化管理与建设中心总经理束炼撰写并投递参与“数据猿年度金猿策划活动——2023大数据产业年度优秀CIO榜单及奖项”评选。 大数据产业创新服务媒体 ——聚焦数据 改变商业 随着数字化转型的浪潮席卷各行各业,中国数字经济已进入快速发展阶…

杨中科 .NETCORE 异步编程

一、 为什么需要异步编程 异步点餐的优点:能同时服务多个客人 异步点餐一定会提升单个客户点餐速度吗? 答案理所当然:不能 图片美化服务例子服务器能够同时服务的请求数量有限 void BeautifyPic (File photo, Response response) {byte[] …

Android中集成FFmpeg及NDK基础知识

前言 在日常App开发中,难免有些功能是需要借助NDK来完成的,比如现在常见的音视频处理等,今天就以ffmpeg入手,来学习下Android NDK开发的套路. JNI和NDK 很多人并不清除JNI和NDK的概念,经常搞混这两样东西,先来看看它们各自的定义吧. JNI和NDK 很多人并不清除JNI和NDK的概念…

智能路由器 端口映射 (UPnP) Padavan内网端口映射配置方法

新版本Padavan 4.4内核的端口映射配置和老版本的不太一样,因为新版本默认是启用的 UPnP端口映射, 同时默认使用的是 IGD UPnP自动端口映射, UPnP名词解释: UPnP通用即插即用,是一组协议的统称,是一种基于TCP/IP、UDP和HTTP的分布式、开放体系&#xff…

CSS3动画效果详解

CSS3动画 在CSS3中,animation属性用于实现元素的动画。 animation属性跟transition属性在功能实现上是非常相似的,都是通过改变元素的属性值来实现动画效果。但是,这两者实际上有着本质的区别 对于transition属性来说,它只能将…

Spring创建的单例对象,存在线程安全问题吗?

这个问题涉及到Spring框架中的Bean的作用域、单例模式的线程安全性以及如何判断和处理线程安全问题。让我们一步步深入探讨这些概念。 Spring Bean的作用域 Spring提供了几种不同的Bean作用域,包括: 1、 Singleton(单例)&#x…

linux软件安装(yum命令)

1.Linux系统的应用商店 操作系统安装软件有许多种方式,一般分为: 下载安装包自行安装 如win系统使用exe文件、msi文件等如mac系统使用dmg文件、pkg文件等 系统的应用商店内安装 如win系统有Microsoft Store商店如mac系统有AppStore商店 Linux命令行…

阳光保险选择OceanBase稳定运行超700天

阳光保险集团成立于 2005 年 7 月,旗下拥有财产保险、人寿保险、信用保证保险、资产管理等多家专业子公司,是全球市场化企业中成长最快的集团公司之一,目前位列中国保险行业前八。随着数字化升级趋势的不断加速,很多企业产生将软硬…

并发编程之ReentrantReadWriteLock详解

目录 ReentrantReadWriteLock介绍 线程进入读锁的前提条件 线程进入写锁的前提条件 ReentrantReadWriteLock三个重要的特性 ReentrantReadWriteLock类 ReentrantReadWriteLock使用读写锁 锁降级 注意事项 ReentrantReadWriteLock结构 ReentrantReadWriteLock读写状态设…

Github上传代码/删除仓库/新建分支的操作流程记录

首先先安装git,安装完git后,看如下操作指令: 输入自己的用户名和邮箱(为注册GITHUB账号时的用户名和邮箱): git config --global user.name "HJX-exoskeleton" git config --global user.email …

Kafka基本介绍

消息队列 产生背景 消息队列:指的数据在一个容器中,从容器中一端传递到另一端的过程 消息(message): 指的是数据,只不过这个数据存在一定流动状态 队列(queue): 指的容器,可以存储数据,只不过这个容器具备FIFO(先进…

Mysql事务的处理

1、事务,就是一组命令的操作。 不过这一组命令,我们有时候需要使用手动提交; 1、使用这组命令可以查询出来现在的提交方式:自动提交(就是命令输入,点击enter后,会不会直接对表格产生修改&#x…

x-cmd pkg | csview - 美观且高性能的 csv 数据查看工具

目录 介绍首次用户功能特点类似工具与竞品进一步阅读 介绍 csview 是一个用于在命令行中查看 CSV 文件的工具,采用 Rust 语言编写的,支持中日韩/表情符号。它允许用户在终端中以表格形式查看 CSV 数据,可以对数据进行排序、过滤、搜索等操作…

关于html导出word总结一

总结 测试结果不理想,html-to-docx 和 html-docx-js 最终导出的结果 都 差强人意,效果可以见末尾的附图 环境 "electron": "24.3.0" 依赖库 html-docx-js html-docx-js - npm html-to-docx html-to-docx - npm file-saver…

Linux技术,winSCP连接服务器超时故障解决方案

知识改变命运,技术就是要分享,有问题随时联系,免费答疑,欢迎联系! 故障现象 使用 sftp 协议连接主机时, 明显感觉缓慢且卡顿,并且时常出现如下报错: 点击重新连接后,又有概率重新连接上; 总之在"连接上"和&…

前端js调用Lodop实现云打印

一、下载Lodop控件 官网:下载中心 - Lodop和C-Lodop官网主站 二、解压后安装 双击进行安装,里面有些页面文件是一些教程案例 勾选云服务工作模式 安装成功会自动启动 浏览器访问地址:http://localhost:8000/ 首页最下面有个教程案例跳转地址&…

kali_linux换源教程

vim /etc/apt/sources.list #阿里云deb http://mirrors.aliyun.com/kali kali-rolling main non-free contribdeb-src http://mirrors.aliyun.com/kali kali-rolling main non-free contrib#清华大学源deb http://mirrors.tuna.tsinghua.edu.cn/kali kali-rolling main contrib…