函数式编程
函数式编程(通常简称为FP)是指通过符合纯函数来构建软件的过程。它避免了共享状态(share state),易变的数据(mutable data)以及副作用(side-effects)。函数式编程是一种编程范式,一种软件构建的思维方式。
以下这些名词定义中蕴含了许多思想,只有理解了它们,才能够开始掌握函数式编程真正的意义:
- 纯函数(Pure functions)
- 函数复合(Function composition)
- 避免共享状态(Avoid shared state)
- 避免改变状态(Avoid mutating state)
- 避免副作用(Avoid side effects)
1.纯函数
- 给它同样的输入,总是返回同样的结果;
- 没有副作用;
纯函数有着许多对函数式编程而言非常重要的属性,包括引用透明(你可以将一个函数调用替换成它的结果值,而不会对程序的运行造成影响。详细:什么是纯函数
2.函数复合
- 函数复合是结合两个或多个函数,从而产生一个新函数或进行某些计算的过程。eg:
f(g(x))
详细:函数复合
3.共享状态
- 共享状态 的意思是任意变量、对象或者内存空间存在于共享作用域下,或者作为对象的属性在各个作用域之间被传递。共享作用域包括全局作用域和闭包作用域。
同步竞争和调用时序变更导致的问题都是共享状态常见的bug。
- 同步竞争:举个例子,操作A向服务器请求改变B的状态,然后马上用C操作向服务器发送请求,该请求也会改变B的状态,不幸的是C操作有可能早于A返回,这就导致了同步竞争的bug。
- 调用时序变更:这个好理解,因为共享状态操作是有时序的,如果颠倒执行顺序就会导致共享状态出现错乱。
4.不可变性
一个不可变的(immutable)对象是指一个对象不会在它创建之后被改变。不可变性是函数式编程的一个核心概念,因为没有它,你的程序中的数据流是有损的。
JavaScript 提供了一个方法,能够浅冻结一个对象:
|
|
但是深层仍旧可以被改变。
在许多函数式编程语言中,有特殊的不可变数据结构,被称为 trie 数据结构(trie 的发音为 tree),这一结构有效地深冻结 —— 意味任何属性无论它的对象层级如何都不能被改变。
有一些 JavaScript 的库使用了 tries,包括 Immutable.js 和 Mori。
immutable更多使用实例:10 Tips for Better Redux Architecture。
ps: 在 JavaScript 中,很重要的一点是不要混淆了 const
和不变性。const
创建一个变量绑定,让该变量不能再次被赋值。const
并不创建不可变对象。你虽然不能改变绑定到这个变量名上的对象,但你仍然可以改变它的属性,这意味着 const
的变量仍然是可变的,而不是不可变的。
5.副作用
副作用是指除了函数返回值以外,任何在函数调用之外观察到的应用程序状态改变。副作用包括:
- 改变了任何外部变量或对象属性(例如,全局变量,或者一个在父级函数作用域链上的变量)
- 写日志
- 在屏幕输出
- 写文件
- 发网络请求
- 触发任何外部进程
- 调用另一个有副作用的函数
6.使用高阶函数提升重用性
高阶函数指的是一个函数以函数为参数,或以函数为返回值,或者既以函数为参数又以函数为返回值。高阶函数经常用于:
- 抽象或隔离行为、作用,异步控制流程作为回调函数,promises,monads,等等……
- 创建可以泛用于各种数据类型的功能
- 部分应用于函数参数(偏函数应用)或创建一个柯里化的函数,用于复用或函数复合。
- 接受一个函数列表并返回一些由这个列表中的函数组成的复合函数。
函数式编程倾向于复用一组通用的函数功能来处理数据。面向对象编程倾向于把方法和数据集中到对象上。那些被集中的方法只能用来操作设计好的数据类型,通常是那些包含在特定对象实例上的数据。
在函数式编程里,对任何类型的数据一视同仁。同样的 map() 操作可以 map 对象、字符串、数字或任何别的类型,因为它接受一个函数参数,来适当地操作给定类型。函数式编程通过使用高阶函数来实现这一技巧。
关于高阶函数就不展开了。自行google。
7.命令式 vs 声明式
函数式编程是声明式的,而不是命令式的,应用程序的状态通过纯函数流转。
- 声明式:意思是说程序逻辑不需要通过明确描述控制流程来表达。程序抽象了控制流过程,花费大量代码描述的是数据流:即做什么。
- 命令式:程序花费大量代码来描述用来达成期望结果的特定步骤 —— 控制流:即如何做。
|
|
8.结论
函数式编程偏好:
- 使用纯函数而不是使用共享状态和副作用
- 让可变数据成为不可变的
- 用函数复合替代命令控制流
- 使用高阶函数来操作许多数据类型,创建通用、可复用功能取代只是操作集中的数据的方法。
- 使用声明式而不是命令式代码(关注做什么,而不是如何做)
- 使用表达式替代语句