函数式编程 - 组合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.mzph.cn/news/619302.shtml

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

相关文章

工业级安卓PDA超高频读写器手持掌上电脑,RFID电子标签读写器

掌上电脑,又称为PDA。工业级PDA的特点就是坚固,耐用,可以用在很多环境比较恶劣的地方。 随着技术的不断发展,加快了数字化发展趋势,RFID技术就是RFID射频识别及技术,作为一种新兴的非接触式的自动识别技术&…

python处理目录下文本文件去除空格和空行

一:实现思路: 要想实现去除某个目录下所有txt文件的空格,需要循环遍历一个目录下的所有文件, 获取文件的每行数据去除空格以后,重新保存数据到当前当前文件中。 处理空格,我们使用正则,这样可以去除字符串…

Java并查集设计以及路径压缩实现

Java全能学习面试指南:https://javaxiaobear.cn 并查集是一种树型的数据结构 ,并查集可以高效地进行如下操作: 查询元素p和元素q是否属于同一组合并元素p和元素q所在的组 1、并查集的结构 并查集也是一种树型结构,但这棵树跟我们之…

uniapp 如何使用echarts 以及解决tooltip自定义不生效问题

使用的是echarts-for-wx插件&#xff1b; 正常写法案例&#xff1a;给tooltip数值加个% <template><view><uni-ec-canvas class"uni-ec-canvas"id"uni-ec-canvas"ref"canvas"canvas-id"uni-ec-canvas":ec"ec&quo…

【数据库】sql优化有哪些?从query层面和数据库层面分析

目录 归纳sql本身的优化数据库层面的优化 归纳 这类型问题可以称为&#xff1a;Query Optimization&#xff0c;从清华AI4DB的paper list中&#xff0c;该类问题大致可以分为&#xff1a; Query RewriterCardinality EstimationCost EstimationPlan Optimization 从中文的角…

SkipList 的索引过程,能否越两级搜索

“SkipList 的索引过程&#xff0c;能否越两级搜索&#xff1f;” 昨天&#xff0c;一个工作 7 年的粉丝&#xff0c;去某外包公司面试&#xff0c;被问到这个问题不知道该怎么回答。 今天正好有空&#xff0c;给大家分享一下这个问题的回答思路。 对了&#xff0c;这个问题…

ZooKeeper 实战(四) Curator Watch事件监听

文章目录 ZooKeeper 实战(四) Curator Watch事件监听0.前言1.Watch 事件监听概念2.NodeCache2.1.全参构造器参数2.2.代码DEMO2.3.日志输出 3.PathChildrenCache3.1.全参构造器参数3.2.子节点监听时间类型3.2.代码DEMO 4.TreeCache4.1.构造器参数4.2.代码DEMO4.3.日志输出 ZooKe…

Flink(十二)【容错机制】

前言 最近已经放假了&#xff0c;但是一直在忙一个很重要的自己的一个项目&#xff0c;用 JavaFX 和一个大数据组件联合开发一个功能&#xff0c;也算不枉我学了一次 JavaFX&#xff0c;收获很大&#xff0c;JavaFX 它作为一个 GUI 开发语言&#xff0c;本质还是 Java&#xff…

MSF流量加密

1、背景介绍 在MSF中生成shell&#xff0c;并上线运行时。都是通过http https tcp等协议传输。虽然MSF本身会对流量进行加密&#xff0c;但MSF太出名以致于其加密特征容易被IPS&#xff0c;WAF等可以检测带有攻击的特征的设备拦截或记录。 2、生成 SSL 证书 openssl req -x50…

关于运维·关于数据库面试题

目录 一、数据库类型 二、数据库引擎 三、mysql数据库类型 四、mysql的约束添加 五、主从复制原理 六、主从方式有几种 七、mysql主从数据不一致的原因 八、mysql的优化 九、什么是事务的特征 十、数据库读写分离的好处 十一、怎样优化sql语句 十二、mysql的同步方…

谷粒商城-商品服务-品牌管理-阿里云云存储+JSR303数字校验+统一异常处理

阿里云云存储OSS 分布式系统上传文件 分布式系统上传文件 单体应用上传&#xff1a;上传文件到服务器&#xff0c;想获取文件时再向服务器发请求获取文件。 分布式系统上传&#xff1a; 因为有多台服务器&#xff0c;为防止负载均衡导致获取文件时没找到对应的服务器&#xf…

实用编程调试技巧

目录 一、调试的基本步骤 二、Debug和Release的介绍 三、Windows环境调试介绍 1.调试环境的准备 2.学会快捷键 最常用的几个快捷键&#xff1a; 断点应用举例&#xff1a; 3.调试的时候查看程序当前信息 &#xff08;1&#xff09…

GitHub注册新账号的操作流程(详细)

目录 第一步 进入官网&#xff0c;点击右上角的"Sign up" 第二步 输入email地址 第三步 设置密码 第四步 输入昵称 第五步 根据个人喜好决定要不要接收GitHub的邮件推送。然后回答他们的验证问题 第六步 输入验证码 我在注册github账号时遇到过一些阻碍&#x…

软件测试|教你使用Python绘制正多边形

简介 绘制正多边形是Python图形编程的基本任务之一。在本文中&#xff0c;我将为你提供一个使用Python绘制正多边形的详细教程&#xff0c;并提供一个示例代码。我们将使用Python的Turtle库来进行绘制。 步骤1&#xff1a;导入Turtle库 我们需要先安装好Python环境&#xff…

Shiro框架:Shiro内置过滤器源码解析

目录 1. 常见项目中过滤器配置 2.Url访问控制配置解析为内置过滤器 2.1 DefaultFilterChainManager构造并注册内置过滤器 2.2 构造过滤器链 3. Shiro内置过滤器解析 3.1 内置过滤器概览 3.2 公共继承类解析 3.2.1 顶层Filter接口 3.2.2 AbstractFilter 3.2.3 Nameab…

二十几种未授权访问漏洞合集

未授权访问漏洞是一个在企业内部非常常见的问题&#xff0c;这种问题通常都是由于安全配置不当、认证页面存在缺陷&#xff0c;或者压根就没有认证导致的。当某企业对外的服务端口、功能无限制开放&#xff0c;并且对用户的访问没有做任何限制的时候&#xff0c;可能会泄露出某…

PLC数组队列搜索FC(SCL代码+梯形图程序)

根据输入数据搜索输入数据队列中和输入数据相同的数,函数返回其所在队列的位置。这里我们需要用到博途PLC的数组指针功能,有关数组指针的详细使用方法,可以参考下面文章: 博途PLC数组指针: https://rxxw-control.blog.csdn.net/article/details/134761364 区间搜索FC …

常用计算电磁学算法特性与电磁软件分析

常用计算电磁学算法特性与电磁软件分析 参考网站&#xff1a; 计算电磁学三大数值算法FDTD、FEM、MOM ADS、HFSS、CST 优缺点和应用范围详细教程 ## 基于时域有限差分法的FDTD的计算电磁学算法&#xff08;含Matlab代码&#xff09;-框架介绍 参考书籍&#xff1a;The finite…

【python】06.函数和模块的使用

函数和模块的使用 在讲解本章节的内容之前&#xff0c;我们先来研究一道数学题&#xff0c;请说出下面的方程有多少组正整数解。 事实上&#xff0c;上面的问题等同于将8个苹果分成四组每组至少一个苹果有多少种方案。想到这一点问题的答案就呼之欲出了。 可以用Python的程序来…

Spring Boot 整合支付宝实现在线支付方案(沙箱环境)

文章目录 1.理解沙箱环境2.沙箱环境接入准备2.1 访问开发者控制台2.2 获取重要信息2.3 处理秘钥 3.接入支付宝支付的流程4.实现支付4.1 添加 SDK 依赖4.2 创建配置类4.3 支付宝订单管理接口实现流程4.4 支付宝支付接口实现流程 5.支付宝支付功能演示7.总结 TIP&#xff1a;对于…