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

入门简介

组合两个函数

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 初始值。

实现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

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

  1. 翻译连载 | JavaScript轻量级函数式编程-第4章:组合函数 |《你不知道的JS》姊妹篇...

    原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTM ...

  2. 翻译连载 |《你不知道的JS》姊妹篇 |《JavaScript 轻量级函数式编程》- 第 4 章:组合函数...

    原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTM ...

  3. 翻译连载 | JavaScript轻量级函数式编程-第4章:组合函数 |《你不知道的JS》姊妹篇

    原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTM ...

  4. 翻译连载 | JavaScript轻量级函数式编程-第4章:组合函数 |《你不知道的JS》姊妹篇... 1

    原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTM ...

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

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

  6. 函数式编程:FlutterDart中的组合

    本文翻译自: Composition in Flutter & Dart 在 Flutter & Dart 中使用组合创建模块化应用程序. 什么是组合? 在dictionary.com ...

  7. JavaScript函数式编程(纯函数、柯里化以及组合函数)

    JavaScript函数式编程(纯函数.柯里化以及组合函数) 目录 JavaScript函数式编程(纯函数.柯里化以及组合函数) 前言 1.纯函数 1.1.纯函数的概念 1.2.副作用 1.3.纯函数 ...

  8. Python函数式编程系列002:水管模型和compose

    水管模型 这一章,我们要回到一个问题,到底函数式和过程式的编程思路到底在哪里?我们这里提供一个形象的比喻. 过程式--屋子物件 过程式思维里,每一个可变变量.函数/过程指称的符号,类似告诉你一个屋子的 ...

  9. javascript函数式_JavaScript中的函数式编程—结合实际示例(第1部分)

    javascript函数式 by rajaraodv 通过rajaraodv JavaScript中的函数式编程-结合实际示例(第1部分) (Functional Programming In Jav ...

最新文章

  1. 求求你别在用IF ELSE校验参数了
  2. 记一次SQL Server2005导入Oracle10G的折腾过程【供多种数据库导入导出数据的C#程序源码参考】...
  3. 服务器网卡显示100m,千兆网线8根线都通了怎么还是显示100M,网卡都是能用1000M的...
  4. aop在项目中的实际运用_【消防验收】防火玻璃在实际运用中的四大关键问题
  5. 同步外部接口数据的一些记录和分享
  6. Android的简介
  7. 整天做办公室的我们要注意饮食啊
  8. 11-windows下卸载Orcale
  9. 去掉源代码里的debug标致
  10. 速修复!VMware vCenter Server 所有版本受严重的 RCE 漏洞影响
  11. 对Mac硬盘重新分区后如何恢复丢失的数据?
  12. Git远程操作详解(clone、remote、fetch、pull、push)
  13. php红包现金,php实现微信支付之现金红包
  14. 泥瓦匠:程序猿为啥要坚持写原创技术博客?
  15. 绘制图形与3D增强技巧(三)----三角形图元TRANGLE
  16. Django邮件应用--QQ邮箱、网易邮箱(二)
  17. python从入门到撩妹 2 —— 30行代码实现520小彩蛋
  18. 树链剖分——树链剖分模板
  19. 国内外十大ERP软件系统排名!
  20. 国产化服务器内网安装onlyoffice

热门文章

  1. 最新Butterknife集成 全部方法(完整版)
  2. iReport 4.1 报表、子报表、主从报表、合计、实例解析
  3. WebCore中的渲染机制(二):块和内嵌(Blocks and Inlines)
  4. Spring Cloud 基于Consul 实现服务注册与发现
  5. Android开发常用轮子
  6. Python闭包与装饰器
  7. 多线程同步中sleep与wait区别
  8. 计算机领域中随处可见的抽象
  9. SDNU 1464.最大最小公倍数(思维)
  10. NET Core的代码安全分析工具 - Security Code Scan