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

函数式编程中有一个比较重要的概念就是函数组合(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.rhkb.cn/news/238585.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

开源ERP系统Odoo安装部署并结合内网穿透实现公网访问本地系统

文章目录 前言1. 下载安装Odoo:2. 实现公网访问Odoo本地系统:3. 固定域名访问Odoo本地系统 前言 Odoo是全球流行的开源企业管理套件,是一个一站式全功能ERP及电商平台。 开源性质:Odoo是一个开源的ERP软件,这意味着企…

SpringSecurity入门demo(一)集成与默认认证

一、集成与默认认证: 1、说明:在引入 Spring Security 项目之后,没有进行任何相关的配置或编码的情况下,Spring Security 有一个默认的运行状态,要求在经过 HTTP 基本认证后才能访问对应的 URL 资源,其默认…

案例分享:当前高端低延迟视频类产品方案分享(内窥镜、记录仪、车载记录仪、车载环拼、车载后视镜等产品)

若该文为原创文章,转载请注明出处 本文章博客地址:https://hpzwl.blog.csdn.net/article/details/135439369 红胖子(红模仿)的博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结…

金融科技革命:数字化如何塑造未来经济_光点科技

当今世界,数字化不仅是一种趋势,更是深刻重塑经济和金融领域的关键力量。在这个过程中,金融科技(FinTech)崭露头角,成为革命性变化的代名词。以下是数字化技术在经济和金融领域的几个关键应用,它…

运放负反馈

学习记录所使用书籍为西安交通大学杨建国教授著《新概念模拟电路》,可在ADI官网下载PDF版学习。 运算放大器,英文为 Operational Amplifier,简写 OA 或 OPA,中文简称为运放。 理想运算放大器如图所示,它具有两个差分的…

mybatisPlus 将List<String>字段转成json字符串,使用JacksonTypeHandler以及自定义类型处理器实现

文章目录 场景使用JacksonTypeHandler实现类型转换自定义StringListTypeHandler处理器实现 场景 项目中经常需要将List转成json存储到数据库中, mybatisPlus默认实现了JacksonTypeHandler,GsonTypeHandler,FastjsonTypeHandler,也可以自定义类…

观成科技-加密C2框架EvilOSX流量分析

工具简介 EvilOSX是一款开源的,由python编写专门为macOS系统设计的C2工具,该工具可以利用自身释放的木马来实现一系列集成功能,如键盘记录、文件捕获、浏览器历史记录爬取、截屏等。EvilOSX主要使用HTTP协议进行通信,通信内容为特…

Java 线程

1. 实现多线程的 2 种方式 Oracle 官网的文档中给出了 2 种实现多线程的方式: 实现 Runnable 接口;继承 Thread 类。 以上两种方式都会调用 Thread.run() 方法,区别是: 实现 Runnable 接口,只是执行 Thread.run() …

Linux--进程状态与优先级

概念 进程指的是程序在执行过程中的活动。进程是操作系统进行资源分配和调度的基本单位。 进程可以看作是程序的一次执行实体,它包含了程序代码、数据以及相关的执行上下文信息。操作系统通过创建、调度和管理多个进程来实现对计算机系统资源的有效利用。 每个进程…

Navicat 技术干货 | 为 MySQL 表选择合适的存储引擎

MySQL 是最受欢迎的关系型数据库管理系统之一,提供了不同的存储引擎,每种存储引擎都旨在满足特定的需求和用例。在优化数据库和确保数据完整性方面,选择合适的存储引擎是至关重要的。今天,我们将探讨为 MySQL 表选择合适的存储引擎…

0基础学java-day25(JDBC 和数据库连接池)

一、JDBC概述 1 基本介绍 2 简单模拟 package com.hspedu.jdbc.myjdbc;/*** author 林然* version 1.0* 我们规定的 jdbc 接口(方法)*/ public interface JdbcInterface {//连接public Object getConnection() ;//crudpublic void crud();//关闭连接public void close(); }pac…

C++|47.动态数组 48.C++的std:vector使用优化

动态数组 动态数组叫vector,也是一种定义好的类/数据结构。“定义好”意味着 vector处在std命名空间之中。 vector的存在代表着一种可以调用的数据结构,不用 动态的意思是可以将该数组的大小进行动态调整。 也就意味着起初vector是没有固定大小的。 它是…

QFN封装对国产双轴半自动划片机的性能有哪些要求?

1. 高精度切割:QFN封装要求芯片的尺寸和形状误差要尽可能小,因此对国产双轴半自动划片机的切割精度提出了高要求。高精度的切割能够提高封装的良品率和稳定性。 2. 快速和稳定:QFN封装生产需要快速、稳定的生产过程,因此对国产双轴…

网页屏幕适配通透了

一,如果设计尺寸固定 那就按照固定尺寸开发 一般都是1920*1080 二,需要适配多种像素屏幕(大屏可视化) 可使用媒体查询设置多套css样式或者使用自适应单位,%,vw,vh 最好解决方案rem&#xff…

mysql原理--redo日志2

1.redo日志文件 1.1.redo日志刷盘时机 我们前边说 mtr 运行过程中产生的一组 redo 日志在 mtr 结束时会被复制到 log buffer 中,可是这些日志总在内存里呆着也不是个办法,在一些情况下它们会被刷新到磁盘里,比如: (1). log buffer…

答疑解惑:核技术利用辐射安全与防护考核

前言 最近通过了《核技术利用辐射安全与防护考核》,顺利拿到了合格证。这是从事与辐射相关行业所需要的一个基本证书,考试并不难,在此写篇博客记录一下主要的知识点。 需要这个证书的行业常见的有医疗方面的,如放疗,…

社会科学杂志社会科学杂志社社会科学编辑部2023年第12期部分目录

铁路部门档案管理中存在的问题及对策 尚芝维 公共图书馆共享服务模式分析 高翔 关于加强国有企业固定资产管理的对策 任美琪 大数据时代高校档案管理人才队伍建设策略 胡永芳 数据治理背景下档案数据馆员能力建设研究 许颖 新时代事业单位档案管理人才培养…

二叉树题目:从前序与后序遍历序列构造二叉树

文章目录 题目标题和出处难度题目描述要求示例数据范围 前言解法一思路和算法代码复杂度分析 解法二思路和算法代码复杂度分析 题目 标题和出处 标题:从前序与后序遍历序列构造二叉树 出处:889. 从前序与后序遍历序列构造二叉树 难度 7 级 题目描述…

互联网上门洗衣洗鞋工厂系统搭建;

随着移动互联网的普及,人们越来越依赖手机应用程序来解决生活中的各种问题。通过手机预约服务、购买商品、获取信息已经成为一种生活习惯。因此,开发一款上门洗鞋小程序,可以满足消费者对于方便、快捷、专业的洗鞋服务的需求,同时…

模拟瑞幸的购物车

是根据渡一大师课来写的&#xff0c;如有什么地方存在问题&#xff0c;还请大家在评论区指出来。ど⁰̷̴͈꒨⁰̷̴͈う♡&#xff5e; index.html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta http…