资讯专栏INFORMATION COLUMN

函数式编程 - 组合compose

zhangrxiang / 1702人阅读

摘要:函数式编程中有一个比较重要的概念就是函数组合组合多个函数,同时返回一个新的函数。深入理解认识函数式编程里面跟类似的方法,就是。主要作用也是组合多个函数,称之为流,肯定得按照正常方法,从左往右调用函数,与调用方法相反。

函数式编程中有一个比较重要的概念就是函数组合(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,也让我们了解了组合的特点,接着我们看看如何组合更多的函数,因为在实际应用中,不会像入门介绍的代码那么简单。

主要注意几个关键点:

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

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

pipe 部分,利用reduce实现,反过来看,compose就可以利用reduceRight

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

利用递归

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

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

利用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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/102266.html

相关文章

  • 函数编程组合

    摘要:在函数式编程的组合中,我们是从右到左执行的,上述的例子中我们借助函数实现组合,当然,我们也可以用自己的方式实现。小结函数式编程随着多核的发展,开始再次出现在我们的视野中,有时候也会担心过于吹捧函数式,反而落入俗套。 程序的本质是什么?数据结构+算法!!!我想这也是很多程序员给出的答案,我自己也认可这一观点,当我们了解了某一门编程语之后,接下来我们面对的往往是数据结构和算法的学习。而现在...

    Jinkey 评论0 收藏0
  • JavaScript函数编程函数组合函数compose和pipe的实现

    摘要:函数组合是函数式编程中非常重要的思想,它的实现的思路也没有特别复杂。前者从左向右组合函数,后者方向相反。下面就是一个最简单的可以组合两个函数的在实际应用中,只能组合两个函数的组合函数显然不能满足要求,我们需要可以组合任意个函数的组合函数。 函数组合是函数式编程中非常重要的思想,它的实现的思路也没有特别复杂。有两种函数组合的方式,一种是pipe,另一种是compose。前者从左向右组合函...

    Cristalven 评论0 收藏0
  • JS每日一题:函数编程中代码组合(compose)如何理解?

    摘要:期函数式编程中代码组合如何理解定义顾名思义,在函数式编程中,就是将几个有特点的函数拼凑在一起,让它们结合,产生一个崭新的函数代码理解一个将小写转大写的函数一个在字符后加的函数将两个函数组合起来这里假设我们实现了每日一题每日一题显示结果里上面 20190315期 函数式编程中代码组合(compose)如何理解? 定义: 顾名思义,在函数式编程中,Compose就是将几个有特点的函数拼凑在...

    Kaede 评论0 收藏0
  • 翻译连载 | JavaScript轻量级函数编程-第4章:组合函数 |《你不知道的JS》姊妹篇

    摘要:把数据的流向想象成糖果工厂的一条传送带,每一次操作其实都是冷却切割包装糖果中的一步。在该章节中,我们将会用糖果工厂的类比来解释什么是组合。糖果工厂靠这套流程运营的很成功,但是和所有的商业公司一样,管理者们需要不停的寻找增长点。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson-《You-Dont-Know-JS》作者 关于译者:这是一个流淌...

    JowayYoung 评论0 收藏0
  • JavaScript函数编程,真香之组合(一)

    摘要:组合的概念是非常直观的,并不是函数式编程独有的,在我们生活中或者前端开发中处处可见。其实我们函数式编程里面的组合也是类似,函数组合就是一种将已被分解的简单任务组织成复杂的整体过程。在函数式编程的世界中,有这样一种很流行的编程风格。 JavaScript函数式编程,真香之认识函数式编程(一) 该系列文章不是针对前端新手,需要有一定的编程经验,而且了解 JavaScript 里面作用域,闭...

    mengbo 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<