函数式编程

函数式编程

最近通过一些机缘巧合,有接触到一系列的函数式编程与高阶函数一类的编程。花了点时间整理

编程范式

  • 命令式
  • 声明式
  • 面向对象
  • 函数式

函数式编程

函数式编程 (通常简称为 FP)是指通过复合纯函数来构建软件的过程。

它避免了共享的状态(share state)易变的数据(mutable data)
以及副作用(side-effects)

函数式编程是声明式而不是命令式,并且应用程序状态通过纯函数流转。对比面向对象编程,后者的应用程序状态通常是共享并共用于对象方法。

这是一种编程范式

如果你想要了解函数式编程在实际中的意义,你需要从理解那些核心概念开始:

  • 纯函数(Pure functions)
  • 函数复合(Function composition)
  • 避免共享状态(Avoid shared state)
  • 避免改变状态(Avoid mutating state)
  • 避免副作用(Avoid side effects)

纯函数:

纯函数有一下的特征

  • 同样的输入,返回同样的结果
  • 没有副作用
  • 不依赖外部状态

函数复合

是结合两个或多个函数,从而产生一个新函数或进行某些计算的过程。

例如,复合操作 f·g(点号意思是对两者执行复合运算)在 JavaScript 中相当于执行 f(g(x))

共享状态

共享状态 的意思是任意变量、对象或者内存空间存在于共享作用域下,或者作为对象的属性在各个作用域之间被传递。共享作用域包括全局作用域和闭包作用域。

通常,在面向对象编程中,对象以添加属性到其他对象上的方式在作用域之间共享。

不可变性

不可变(immutable) 对象是指一个对象不会在它创建之后被改变。对应地,一个可变的(mutable)对象是指任何在创建之后可以被改变的对象。

副作用

副作用是指除了函数返回值以外,任何在函数调用之外观察到的应用程序状态改变。

副作用包括:

  • 改变了任何外部变量或对象属性
  • 写日志
  • 在屏幕输出
  • 写文件
  • 发网络请求
  • 触发任何外部进程
  • 调用另一个有副作用的函数

在函数式编程中,副作用被尽可能避免,这使得程序的作用更容易理解,也使得程序更容易被测试。

你现在需要做的是要从你的软件中隔离副作用行为。如果你让副作用与你的程序逻辑分离,你的软件将会变得更易于扩展、重构、调试、测试和维护。

这也是为什么大部分前端框架鼓励我们分开管理状态和组件渲染,采用松耦合的模型。

区别

函数式编程倾向于复用一组通用的函数功能来处理数据。

面向对象编程倾向于把方法和数据集中到对象上。那些被集中的方法只能用来操作设计好的数据类型,通常是那些包含在特定对象实例上的数据。

命令式 && 声明式

函数式编程是一个声明式范式,意思是说程序逻辑不需要通过明确的描述控制流程来表达

命令式 程序花费大量代码描述所其外结果的步骤——控制流:如何做

声明式 程序抽象了控制流程的过程,花费大量代码描述数据流:即做什么

举个例子,下面是一个用 命令式 方式实现的 mapping 过程,接收一个数值数组,并返回一个新的数组,新数组将原数组的每个值乘以 2:

1
2
3
4
5
6
7
8
const doubleMap = numbers => {
const doubled = [];
for (let i = 0; i < numbers.length; i++) {
doubled.push(numbers[i] * 2);
}
return doubled;
};
console.log(doubleMap([2, 3, 4])); // [4, 6, 8]

而实现同样功能的 声明式 mapping 用函数 Array.prototype.map() 将控制流抽象了,从而我们可以表达更清晰的数据流:

1
2
const doubleMap = numbers => numbers.map(n => n * 2);
console.log(doubleMap([2, 3, 4])); // [4, 6, 8]

命令式 代码中频繁使用语句。语句是指一小段代码,它用来完成某个行为。通用的语句例子包括 forifswitchthrow,等等……

声明式 代码更多依赖表达式。表达式是指一小段代码,它用来计算某个值。表达式通常是某些函数调用的复合、一些值和操作符,用来计算出结果值。

以下都是表达式:

1
2
3
2 * 2
doubleMap([2, 3, 4])
Math.max(4, 3, 2)

通常在代码里,你会看到一个表达式被赋给某个变量,或者作为函数返回值,或者作为参数传给一个函数。在被赋值、返回或传递之前,表达式首先被计算,之后它的结果值被使用。

结论

函数式编程偏好:

  • 使用纯函数而不是使用共享状态和副作用
  • 让可变数据成为不可变的
  • 用函数复合替代命令控制流
  • 使用高阶函数来操作许多数据类型,创建通用、可复用功能取代只是操作集中的数据的方法。
  • 使用声明式而不是命令式代码(关注做什么,而不是如何做)
  • 使用表达式替代语句
  • 使用容器与高阶函数替代多态

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×