mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-25 12:27:15 +00:00
220 lines
7.1 KiB
Markdown
220 lines
7.1 KiB
Markdown
# Swift 函数式编程
|
||
|
||
## 定义
|
||
|
||
函数式编程(Funtional Programming,简称 FP)是一种编程范式,也就是如何编写程序的方法论
|
||
|
||
- 主要思想:把计算过程尽量分解成一系列可复用函数的调用
|
||
- 主要特征:函数是“第一等公民”。函数与其他数据类型一样的地位,可以赋值给其他变量,也可以作为函数参数、函数返回值
|
||
|
||
|
||
|
||
函数式编程最早出现在 LISP 语言,绝大部分的现代编程语言也对函数式编程做了不同程度的支持,比如:Haskell、JavaScript、Python、Swift、Kotlin、Scala 等
|
||
|
||
|
||
|
||
函数式编程中几个常用的概念:
|
||
|
||
- Higher-Order Function、Function Currying
|
||
- Functor、Applicative Functor、Monad
|
||
|
||
|
||
|
||
## 高阶函数
|
||
|
||
高阶函数是至少满足下列一个条件的函数:
|
||
|
||
- 接受一个或多个函数作为输入(map、filter、reduce等)
|
||
- 返回一个函数
|
||
|
||
FP中到处都是高阶函数
|
||
|
||
|
||
|
||
## 柯里化(Currying)
|
||
|
||
将一个接受多个参数的函数变换成为一系列只接受单个参数的函数
|
||
|
||
Demo:
|
||
|
||
```swift
|
||
// 函数式编程,为了过程的复用
|
||
let num = 10
|
||
func add(_ v: Int) -> (Int) -> Int {{ $0 + v }}
|
||
func sub(_ v: Int) -> (Int) -> Int { { $0 - v } }
|
||
func multiple( _ v: Int) -> (Int) -> Int { { $0 * v }}
|
||
func divide(_ v: Int) -> (Int) -> Int { { $0 / v } }
|
||
func mod(_ v: Int) -> (Int) -> Int { { $0 % v }}
|
||
|
||
// 函数合成,泛型
|
||
infix operator >>> : AdditionPrecedence
|
||
func >>><A, B, C>(_ f1: @escaping (A) -> B,
|
||
_ f2: @escaping (B) -> C)
|
||
-> (A) -> C {
|
||
{ f2(f1($0)) }
|
||
}
|
||
|
||
// result = ((((x + 3)*5) - 1 )%10)/2
|
||
let fn = add(3) >>> multiple(5) >>> sub(1) >>> mod(10) >>> divide(2)
|
||
|
||
print(fn(num))
|
||
|
||
|
||
func multipleAdd(_ v1: Int) -> ((Int) -> ((Int) -> Int)) {
|
||
return { v2 in
|
||
return { v3 in
|
||
return
|
||
v1 + v2 + v3
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
- multipleAdd(1),调用函数,传递1给参数V3,此时继续返回一个函数。
|
||
- multipleAdd(1)(2) 拿着上一步返回的函数,再去调用,传入的2给参数V2.此时继续返回一个函数
|
||
- multipleAdd(1)(2)(3) 拿着上一步返回的函数,再去调用,此时传入的3给参数V1,此时则执行相加操作。V1 + V2 + V3
|
||
*/
|
||
let rs = multipleAdd(1)(2)(3)
|
||
print(rs)
|
||
```
|
||
|
||
参数对应如下图圈选部分
|
||
|
||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftFunctionalProgrammingCurrying.png" style="zoom:25%">
|
||
|
||
|
||
|
||
Demo2:将一个三个参数的函数变成柯里化
|
||
|
||
```swift
|
||
func addThree(_ v1: Int, _ v2: Int, _ v3: Int) -> Int { v1 + v2 + v3 }
|
||
func addThree(_ v1: Int) -> ((Int) -> (Int) -> Int) {
|
||
return { v2 in
|
||
return { v3 in
|
||
return v1 + v2 + v3
|
||
}
|
||
}
|
||
}
|
||
|
||
func currying<A, B, C, D>(_ fn: @escaping (A, B, C) -> D) -> ((A) -> ((B) -> ((C) -> D))) {
|
||
return { v1 in
|
||
return { v2 in
|
||
return { v3 in
|
||
fn(v1, v2, v3)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
print(addThree(1, 2, 3)) // 6
|
||
print(addThree(1)(2)(3)) // 6
|
||
print(currying(addThree)(1)(2)(3)) // 6
|
||
```
|
||
|
||
|
||
|
||
## 函子
|
||
|
||
### 概念
|
||
|
||
在函数式编程中,**函子(Functor)** 是一个核心概念,它表示一种可以**被映射**的容器或结构。简单来说,函子能够接受一个函数,将该函数应用到其内部的值上,并返回一个**保持原有结构**的新函子。
|
||
|
||
函子存在3个特性:
|
||
|
||
- **容器性**:函子是一个包装值的容器(`List` 等)
|
||
- **可映射性**:通过 `map` 方法将函数应用到容器内的值,例如 `var array2 = [1, 2, 3].map { $0 + 1 }`
|
||
- **结构不变性**:映射过程不会改变容器的结构,例如数组的 `map` 方法返回新数组而非其他类型
|
||
|
||
函子提供了一种**安全操作上下文中的值**的方式,是函数式编程中组合和抽象的基础工具。它通过 `map` 方法解耦了“值”和“上下文”,使得代码更模块化、可复用
|
||
|
||
|
||
|
||
### 函子定律
|
||
|
||
合法的函子必须满足以下规则:
|
||
|
||
1. **恒等律**:`fmap id = id`(映射恒等函数后,容器不变)。
|
||
2. **组合律**:`fmap (f . g) = fmap f . fmap g`(函数组合的映射等价于分别映射)
|
||
|
||
|
||
|
||
### 总结
|
||
|
||
像 Array、Optional 这样支持 map 运算的类型,称为函子(Functor)
|
||
|
||
```swift
|
||
// Array<Element>
|
||
@inlinable public func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]
|
||
|
||
// Optional<Wrapped>
|
||
@inlinable public func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U?
|
||
```
|
||
|
||
跳出语言来看,符合函数式编程的语言都存在**函子** 这一概念,符合下面的形式,都可以叫函子
|
||
|
||
```swift
|
||
func map<T>(_ fn: (Element) -> T) -> Type<T>
|
||
```
|
||
|
||
|
||
|
||
## 适用函子(Applicative Functor)
|
||
对任意一个函子 F,如果能支持以下运算,该函子就是一个适用函子
|
||
```swift
|
||
func pure<A>(_ value: A) -> F<A>
|
||
func <*><A, B>(fn: F<(A) -> B>, value: F<A>) -> F<B>
|
||
```
|
||
|
||
Array 可以成为适用函子
|
||
|
||
```swift
|
||
func pure<A>(_ value: A) -> [A] {
|
||
[value]
|
||
}
|
||
|
||
infix operator <*> : AdditionPrecedence
|
||
func <*><A, B>(fn:[(A) -> B], value: [A]) -> [B] {
|
||
var resultArray: [B] = []
|
||
if fn.count == value.count {
|
||
for i in fn.startIndex..<fn.endIndex {
|
||
resultArray.append(fn[i](value[i]))
|
||
}
|
||
}
|
||
return resultArray
|
||
}
|
||
print(pure(10))
|
||
|
||
var array = [{$0 * 2}, {$0 + 10}, {$0 - 5}] <*> [1, 2, 3]
|
||
print(array)
|
||
// console
|
||
[10]
|
||
[2, 12, -2]
|
||
```
|
||
|
||
|
||
|
||
## 单子
|
||
|
||
对任意一个类型 F,如果能支持以下运算,那么就可以称为是一个单子(Monad)
|
||
|
||
```swift
|
||
func pure<A>(_ value: A) -> F<A>
|
||
func flatMap<A, B>(_ value: F<A>, _ fn: (A) -> F<B>) -> F<B>
|
||
```
|
||
|
||
很显然,Array、Optional 都是单子
|
||
|
||
|
||
|
||
“单子”(Monad)是一个抽象概念,它代表了一种设计模式,用于组合计算并管理可能包含副作用的值。单子是一种在函数式编程中用于封装和组合计算的通用结构,它允许程序员以一致的方式处理各种复杂的计算情况,包括错误处理、异步操作、状态管理等。
|
||
|
||
单子通常定义了几个操作,这些操作允许你以统一的方式对封装在单子中的值进行操作。这些操作包括:
|
||
|
||
- `return` 或 `pure`:将一个值“提升”到单子中。
|
||
- `bind` 或 `flatMap`:用于组合单子中的计算,允许你将一个单子的输出作为另一个单子的输入。
|
||
|
||
在 Swift 中,单子并没有像在其他一些语言(如 Haskell 或 Scala)中那样作为语言内建的概念,但你可以通过定义自己的类型和方法来实现单子模式。例如,Swift 中的 `Optional` 类型可以看作是一个简单的单子,它表示一个值可能存在或不存在。`Optional` 提供了 `map` 和 `flatMap` 方法,允许你对封装的值进行链式操作。
|
||
|
||
其他常见的单子实现包括处理异步操作的单子(如 Promise 或 Future),处理错误和异常的单子,以及管理状态的单子(如 State Monad)。
|
||
|
||
单子提供了一种强大的方式来管理复杂性和副作用,使代码更易于理解和测试。然而,它们也增加了抽象层次,可能需要一些时间来适应和理解。在 Swift 中,你可以根据自己的需要选择是否使用单子,以及使用哪种类型的单子。 |