浅谈函数式编程
# # 浅谈函数式编程
程序设计时,各语言只是实现的过程,目的都相同:清晰可读易扩展。表现在设计原则:
可扩展
- 需求变了,能不改动以前的代码,而是拥有扩展的能力(设计模式就是从中总结出来的一些经验)。可重用
- 避免到处是重复的代码,万一逻辑变动,所有地方都得改。模块化
- 拆分模块,使得各模块各司其职,而不是杂柔在一起(大型项目必备)可推理
- 读代码次数比写代码次数多,易于维护可测试
- 代码健壮性
在符合以上基础原则上,可以使用任何语言去实现最终的程序。但现实是各语言都有各自特点以及适用场景,实现同一个功能,可能一个及其简单,一个复杂。比如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)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
以上得知,不同语言受限于语法不同,代码风格不一致。同一种语言(如:js)实现相同的功能,风格也大不一样,如上面的“函数式编程实现”以及“命令式编程实现”。所以函数式编程是一种编程风格,也可以说是编程范式
。
编程范式是如何编写程序的方法论。
# # 函数式编程
以函数作为主要载体的编程方式,用函数去拆解、抽象一般的表达式。它的目的是使用函数来抽象作用在数据之上的控制流和操作
,从而在系统中消除副作用
以及减少对状态的改变
。
函数式编程旨在尽可能的提高代码的无状态性和不变性。要做到这一点,就要学会使用纯函数。纯函数,就是无副作用的函数
。所谓"副作用"(side effect),指的是函数内部与外部互动(最典型的情况,就是修改全局变量的值),产生运算以外的其他结果。 函数式编程强调没有"副作用",意味着函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值。
所以函数式编程有如下特性
:
函数是"第一等公民"
。指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。不修改状态
。不得修改外部变量的值引用透明
。同样的输入,那么函数总是返回同样的结果(单元测试梦寐以求的)无副作用
。调用函数只会计算出结果,不会出现其他效果。
以上决定了函数式编程有如下优点
:
- 语义更加清晰
- 可复用性更高(函数为可调用的最小单位)
- 可维护性更好(只需关注表达式的内部的实现,更易定位bug)
- 作用域局限,副作用少
面向对象编程通过封装变化使得代码易于理解。
函数式编程通过最小变化使得代码易于理解。
# # 常见的函数式编程模型
以函数作为主要载体的编程方式:
- 闭包(Closure)
- 高阶函数。接受1个或多个函数作为输入或输出一个函数的函数。简单说
高阶函数是操作其他函数的函数。
- map
- filter
- reduce
- 柯里化(Currying)
- Currying 为实现多参函数提供了一个递归降解的实现思路——把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数
- 使用场景
- 参数复用
- 延迟执行
- 实现方式
- bind语法糖 使得JSX可以绑定数据,同时延迟执行
- 箭头函数 使得JSX延迟执行
- 自定义curry函数
- 组合(Composing)/ 管道(Pipe)
# # 参考文章
编辑 (opens new window)