# 浅谈函数式编程

程序设计时,各语言只是实现的过程,目的都相同:清晰可读易扩展。表现在设计原则:

  • 可扩展 - 需求变了,能不改动以前的代码,而是拥有扩展的能力(设计模式就是从中总结出来的一些经验)。
  • 可重用 - 避免到处是重复的代码,万一逻辑变动,所有地方都得改。
  • 模块化 - 拆分模块,使得各模块各司其职,而不是杂柔在一起(大型项目必备)
  • 可推理 - 读代码次数比写代码次数多,易于维护
  • 可测试 - 代码健壮性

在符合以上基础原则上,可以使用任何语言去实现最终的程序。但现实是各语言都有各自特点以及适用场景,实现同一个功能,可能一个及其简单,一个复杂。比如JavaScript是一种动态类型语言,函数也是类型的一种(当作对象类型),所以可以把函数当作参数值进行传递(这就是FP(functional programming)中常说的函数天生是“一等公民”)。而Java这种强类型面向对象语言,是无法把定义的函数/方法当作一个参数,传入到另外一个函数/方法中。两者的编程风格区别看以下案例:

// js函数式编程
// 函数作为参数值传入,使得逻辑更清晰并且无污染
[1, 2, 3]
    .filter(function(item) { return item !== 1})
    .map(funciton(item) { return item * 2 })

// js命令式编程
// 相比函数式,1. 更多的中间状态,如mapArr 2. 逻辑可读性差 3. 代码复用差
var arr = [1, 2, 3]
var mapArr = []
for(var i = 0; i < arr.length; i++) {
    if (arr[i] != 1) {
        mapArp.push(arr[i] * 2)
    }
}
// java命令式编程
// 定义的参数互相串行,复用性差
int[] arr = {1, 2, 3};
// filter
List<int> filterArr = new List<>();
for(int i = 0; i < arr.length; i++)
{
    if (arr[i] != 1)
    {
        filterArr.add(arr[i]);
    }
}
// map
int[] result = ...MapArray(filterArr)

以上得知,不同语言受限于语法不同,代码风格不一致。同一种语言(如:js)实现相同的功能,风格也大不一样,如上面的“函数式编程实现”以及“命令式编程实现”。所以函数式编程是一种编程风格,也可以说是编程范式

编程范式是如何编写程序的方法论。

# 函数式编程

以函数作为主要载体的编程方式,用函数去拆解、抽象一般的表达式。它的目的是使用函数来抽象作用在数据之上的控制流和操作,从而在系统中消除副作用以及减少对状态的改变

函数式编程旨在尽可能的提高代码的无状态性和不变性。要做到这一点,就要学会使用纯函数。纯函数,就是无副作用的函数。所谓"副作用"(side effect),指的是函数内部与外部互动(最典型的情况,就是修改全局变量的值),产生运算以外的其他结果。 函数式编程强调没有"副作用",意味着函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值。

所以函数式编程有如下特性

  1. 函数是"第一等公民"。指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。
  2. 不修改状态。不得修改外部变量的值
  3. 引用透明。同样的输入,那么函数总是返回同样的结果(单元测试梦寐以求的)
  4. 无副作用。调用函数只会计算出结果,不会出现其他效果。

以上决定了函数式编程有如下优点

  • 语义更加清晰
  • 可复用性更高(函数为可调用的最小单位)
  • 可维护性更好(只需关注表达式的内部的实现,更易定位bug)
  • 作用域局限,副作用少

面向对象编程通过封装变化使得代码易于理解。

函数式编程通过最小变化使得代码易于理解。

# 常见的函数式编程模型

以函数作为主要载体的编程方式:

  • 闭包(Closure)
  • 高阶函数。接受1个或多个函数作为输入或输出一个函数的函数。简单说高阶函数是操作其他函数的函数。
    • map
    • filter
    • reduce
  • 柯里化(Currying)
    • Currying 为实现多参函数提供了一个递归降解的实现思路——把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数
    • 使用场景
      • 参数复用
      • 延迟执行
    • 实现方式
      • bind语法糖 使得JSX可以绑定数据,同时延迟执行
      • 箭头函数 使得JSX延迟执行
      • 自定义curry函数
  • 组合(Composing)/ 管道(Pipe)

# 参考文章