函数式编程 - 组合compose

栏目: 编程语言 · 发布时间: 6年前

内容简介:函数式编程中有一个比较重要的概念就是函数组合(compose),组合多个函数,同时返回一个新的函数。调用时,compose 非常简单,通过下面示例代码,就非常清楚上面组合了两个函数的compose,也让我们了解了组合的特点,接着我们看看如何组合更多的函数,因为在实际应用中,不会像入门介绍的代码那么简单。

函数式编程中有一个比较重要的概念就是函数组合(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的示例:

参考链接:


以上所述就是小编给大家介绍的《函数式编程 - 组合compose》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Head First Web Design

Head First Web Design

Ethan Watrall、Jeff Siarto / O’Reilly Media, Inc. / 2009-01-02 / USD 49.99

Want to know how to make your pages look beautiful, communicate your message effectively, guide visitors through your website with ease, and get everything approved by the accessibility and usability ......一起来看看 《Head First Web Design》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具