初识函数式编程

一、什么是函数式编程

函数式编程(Functional Programming),又称泛函编程,可以理解为一种约定的编程规范。函数式编程是将电脑运算看做数学上的函数运算,其数学基础来源于λ验算。

函数式编程不是用函数来编程,也不是面向过程编程。函数式编程更注重程序执行的结果,运算过程尽量写成一系列嵌套的函数调用。

二、函数式编程的一些特点
  • 函数是“一等公民”。函数与其他数据类型一样,可以赋值给其他变量,可以作为参数传入别的函数,也可以作为别的函数的返回值。
  • 只有“表达式”,没有“语句”。与指令式编程不同,函数式编程中,没有语句,只有表达式。
  • 没有副作用。所谓“副作用”(side effect),就是指函数内部与外部互动。
  • 不修改状态。函数式编程只会返回新的值,而不会修改值。
  • 引用透明。函数运行只靠参数。
三、函数式编程的核心概念
  1. 纯函数
    满足相同的输入总会得到相同的输出条件的函数就是纯函数。纯函数没有任何可观察的副作用,也不依赖于外部环境的状态。纯函数有很多很棒的特性,如可缓存性。但是纯函数的可拓展性比较差,这一点可以通过柯里化解决。
  2. 函数的柯里化
    柯里化就是传给函数一部分参数,返回一个函数处理剩下的参数。
    柯里化是一种“预加载”函数的方法。
    举个例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 柯里化之前
    function add(x,y){
    return x + y;
    }
    add(1, 2);

    // 柯里化之后
    function addX(y){
    return function(x){
    return x + y;
    }
    }
    addX(2)(1)
  3. 函数组合
    为了解决函数嵌套的问题,我们需要用到函数组合。

    1
    const compose = (f, g) => (x => f(g(x)));

    通过函数组合,我们可以将任意个纯函数连起来,组成我们想要的函数。

  4. Point Free
    Point Free是一种编程风格,Point Free不使用要处理的值,只合成运算过程,也可以叫做“无值”风格,特点是保持代码的简洁和通用。
    Point Free的一种应用是把对象原生的一些方法转化为纯函数。
  5. 声明式与命令式代码
    命令式代码是我们通过一条条指令让计算机去执行动作。
    声明式代码是我们通过写表达式的方式来声明我们想干什么。

    1
    2
    3
    4
    5
    6
    7
    // 命令式
    let parents = [];
    for (var i=0; i<children.length; i++){
    parents.push(children[i].parent);
    }
    //声明式
    let parents = children.map(child => child.parent);
  6. 惰性求值、惰性函数
    在指令式编程中,由于每个函数都有可能改变或依赖于外部的状态,因此必须顺序执行。而在函数式编程中,我们可以把代码的结果延迟到需要时在执行。这样可以最小化计算机要做的工作。

  7. 高阶函数
    函数当参数,把传入的函数封装,然后返回这个封装函数,达到更高程度的抽象。
  8. 尾调用优化
    指函数内部的最后一个动作是函数调用。该调用的返回值,直接返回给函数。函数调用自身,称为递归。如果尾调用自身,就称为尾递归。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 不是尾递归,无法优化
    function factorial(n) {
    if (n === 1) return 1;
    return n * factorial(n - 1);
    }
    //尾递归
    function factorial(n, total) {
    if (n === 1) return total;
    return factorial(n - 1, n * total);
    }

    在“尾调用优化”中,只保留内层函数的调用记录。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用记录只有一项,这将大大节省内存。这就是“尾调用优化”的意义。
    在ES6中第一次明确规定,所有 ECMAScript 的实现,都必须部署“尾调用优化”。这就是说,在 ES6 中,只要使用尾递归,就不会发生栈溢出,相对节省内存。

四、函数式编程有什么好处
  1. 接近自然语言,便于理解
    函数式编程自由度很高,利于写为链式表达式,让人一眼能看明白。

    1
    2
    3
    4
    // 表达式 (1 + 2)* 3 - 4
    subtract(multiply(add(1,2), 3), 4)
    // 函数式表达式
    add(1,2).multiply(3).subtract(4)
  2. 方便管理
    函数式编程不依赖也不会影响外部状态,因此每个函数可以看做独立的单元,易于单元测试和纠错,以及模块化组合。

  3. 易于“并发编程”

  4. 代码热升级
    函数式编程没有副作用,只要保证接口不变,内部实现与外部无关。所以可以在运行状态直接升级代码。

五、函数式编程与JavaScript
  • Rx.js
  • Cycle.js
  • Underscore.js
  • Lodash.js
  • Ramda.js

参考资料:
维基百科
阮一峰的网络日志