函数式编程

定义:

简单说,"函数式编程"是一种"编程范式"(programming paradigm),也就是如何编写程序的方法论。 它属于"结构化编程"的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用。


(1 + 2) * 3 - 4
// 传统的过程式编程
   var a = 1 + 2;
  var b = a * 3;
  var c = b - 4;

// 函数式编程
var result = subtract(multiply(add(1,2), 3), 4);
1
2
3
4
5
6
7
8
9

特点:

  1. 函数是第一等公民

    所谓"第一等公民"(first class),指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。

  2. 只用表达式,不用语句

    "表达式"(expression)是一个单纯的运算过程,总是有返回值;"语句"(statement)是执行某种操作,没有返回值。函数式编程要求,只使用表达式,不使用语句。也就是说,每一步都是单纯的运算,而且都有返回值。 原因是函数式编程的开发动机,一开始就是为了处理运算(computation),不考虑系统的读写(I/O)。"语句"属于对系统的读写操作,所以就被排斥在外。 当然,实际应用中,不做I/O是不可能的。因此,编程过程中,函数式编程只要求把I/O限制到最小,不要有不必要的读写行为,保持计算过程的单纯性。

  3. 没有副作用

    所谓"副作用"(side effect),指的是函数内部与外部互动(最典型的情况,就是修改全局变量的值),产生运算以外的其他结果。 函数式编程强调没有"副作用",意味着函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值。

  4. 不修改状态 函数式编程使用参数保存状态,最好的例子就是递归。

     function reverse(string) {
    if(string.length == 0) {
      return string;
    } else {
      return reverse(string.substring(1, string.length)) + string.substring(0, 1);
    }
  }
1
2
3
4
5
6
7
  1. 引用透明

    指的是函数的运行不依赖于外部变量或"状态",只依赖于输入的参数,任何时候只要参数相同,引用函数所得到的返回值总是相同的。

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
1
2
3
4
5
6
7
8
9
10
11
12

组合任意函数

reduce实现compose函数

const greetings = (name, word) => `${name}, ${word}`
// 大写
const toUpper = x => x.toUpperCase()
// 增加标点
const addPunctuate = s => s + '!'

function compose (...functions) {
        if (functions.length === 0) {
            return functions[0]
        } else {
            return functions.reduce((result, item) => {
                return (...args) => result(item(...args))
            })
        }
    }

console.log(compose(addPunctuate, toUpper, greetings)('tom', '你好')) // TOM,你好!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

通过call和apply方法实现compose函数

    const greetings = (name, word) => `${name}, ${word}`
    // 大写
    const toUpper = x => x.toUpperCase()
    // 增加标点
    const addPunctuate = s => s + '!'
    compose = (...fns)=>{
        if (fns.length === 1) {
            return fns[0]
        } else {
            return (...args)=>{
                var index = 0
                var result = fns[index].apply(this,args)
                while (++index<fns.length){
                    result = fns[index].call(this,result)
                }
                return result
            }
        }
    }

    console.log(compose(greetings,addPunctuate,toUpper,)('tom', '你好'))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

函数柯里化 currying

Currying 为实现多参函数提供了一个递归降解的实现思路——把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数,在某些编程语言中(如 Haskell),是通过 Currying 技术支持多参函数这一语言特性的。

总结:Currying ——只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。

使用示例:

<div onClick={currying(handleOnClick, data)} />
1

两个函数的柯里化

function curriedAdd (x) {

  return function(y) {

    return x + y

  }
}

1
2
3
4
5
6
7
8
9

多个函数的柯里化

    function add(a, b, c, d) {

        return a + b + c + d
    }

    const curryedAdd = curry(add,1,2)
    console.log(curryedAdd(3)(100))
    function curry(fn) {
        let arg = [].slice.call(arguments,1)
        if (arg.length >= fn.length) {
            return fn(...arg)
        }
        return function (...args2) {
            return curry(fn, ...arg, ...args2)
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

性能对比

结果为: 箭头函数 > bind > currying

结语

  1. Currying 在 JavaScript 中是“低性能”的,但是这些性能在绝大多数场景,是可以忽略的。
  2. Currying 的思想极大地助于提升函数的复用性。
  3. Currying 生于函数式编程,也陷于函数式编程。假如没有准备好写纯正的函数式代码,那么 Currying 有更好的替代品。
最后更新时间: 11/14/2019, 5:14:10 PM