函数式编程(Funtional Programming,简称FP)是一种编程范式,也就是如何编写程序的方法论。
一、什么是函数式编程?
1.1. 介绍
主要思想: 把计算过程尽量分解成一系列可复用函数的调用。
主要特征: 函数是”一等公民”(函数与其他数据类型一样的地位,可以赋值给其他变量,也可以作为函数参数、函数返回值)。
函数式编程中几个常用的概念:
- Higher-Order Function、Function Currying
- Functor、Applicative Functor、Monad
延伸:函数式编程最早出现在LISP语言,绝大部分的现代编程语言也对函数式编程做了不同程度的支持,比如JavaScript、Python、Swift、Kotlin、Scala等。
1.2. 实践
1.2.1. 传统写法
示例代码:
1 | var num = 1 |
看到上面的嵌套非常多,如果遇到复杂的场景,整个代码可读写非常差。
1.2.2. 函数式写法
如果把add
函数赋值给一个变量,函数固定加一个数,函数式写法就很方便。
示例代码;
1 | func add(_ v: Int) -> (Int) -> Int { {$0 + v} } |
add()
返回一个函数,调用返回的函数时也可以传入一个参数,最终结果是把这两个参数加起来。而且这样子可以做到函数可复用。
同理,其他函数也是这样函数式处理:
1 | typealias FnInt = (Int) -> Int |
发现如果使用上面的写法,代码依然看起来很臃肿。下面介绍函数合成,可以解决此问题。
二、函数合成
函数合成,其实就是把多个函数合成到一个函数里面。
示例代码(函数合成):
1 | func composite(_ f1: @escaping FnInt, _ f2: @escaping FnInt) -> FnInt { |
这样就把加法和乘法的函数合成到一个函数了,其他函数同理。如果想要函数更加优雅,我们可以自定义运算符。
示例代码(自定义运算符):
1 | infix operator >>> : AdditionPrecedence |
执行fn(num)
最开始进入的是f1
,而f1
的函数返回值就是f2
的参数值,所以这两个地方的参数类型一定是一致的。
为了保证函数的可复用性,使用泛型进行表述:
1 | func >>> <A, B, C> ( |
三、高阶函数
高阶函数是至少满足下列一个条件的函数:
- 接受一个或多个函数作为输入(例如:
map、filter、reduce
等) - 返回一个函数
在FP中到处都是高阶函数。例如上面示例代码中的add、sub、multiple、divide、mod
。
四、柯里化
什么是柯里化(Currying)?
将一个接受多参数的函数变换为一系列只接受单个参数的函数。
示例代码一(基础代码):
1 | func add(_ v1: Int, _ v2: Int) -> Int { v1 + v2 } |
示例代码二(柯里化):
1 | func add(_ v: Int) -> (Int) -> Int { {$0 + v} } |
示例代码一到示例代码二的过程就是柯里化。而且使用通用柯里化函数后,柯里化同类型原函数时只需要调用通用柯里化函数就可以。
Array、Optional
的map
方法接收的参数就是一个柯里化函数。
示例代码三(原函数接收三个参数的柯里化):
1 | func add1(_ v1: Int, _ v2: Int, _ v3: Int) -> Int { v1 + v2 + v3 } |
注意:柯里化函数参数一般是原函数参数的倒序方式。具体需要根据开发需求调整对应参数顺序。
五、函子
像Array、Optional
这样支持map
运算的类型,称为函子(Functor)。
Array
中map
的定义:
1 | public func map<T>(_ transform: (Element) throws -> T) rethrows -> [T] |
Element
是数组内部存放的实体,map
函数返回值类型是数组,闭包表达式返回值和数组内部存放的实体都是泛型T
。满足函子定义的条件。
Optional
中map
的定义:
1 | public func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U? |
Wrapped
是可选类型内部存放的实体,map
函数返回值类型是可选类型,闭包表达式返回值和数组内部存放的实体都是泛型U
。满足函子定义的条件。
比如有一个可选类型,内部包装的是2
,如果要进行+3
操作,则需要把数字2
先解包,+3
操作完成后把结果再放回包装盒内(可选类型),如果盒子是空的,则不作任何操作。下面是图例:
第一步:取值
第二步:操作再包装
第三步:空值不处理
数组map
图例(遍历每一个值,完成后的值放到新的数组里):
5.1. 适用函子
对任意一个函子F
,如果能支持以下运算,该函子就是一个适用函子(Application Functor)。
1 | // 传入任意泛型参数A,最终返回对应类型,该类型的实体 |
Optional
可以成为适用函子:
1 | func pure<A>(_ value: A) -> A? { value } |
Array
可以成为适用函子:
1 | infix operator <*> : AdditionPrecedence |
5.2. 单子
对任意一个类型F
,如果能支持以下运算,那么就可以称为是一个单子(Monad)。
1 | func pure<A>(_ value: A) -> F<A> |
很显然,Array、Optional
都是单子。
注意:有些参考资料会说单子就是函子。
参考资料: