1. 函数式编程基础
1.1 函数式编程概念
1.1.1 概念
函数式编程(Functional Programming)是一种编程范式,它将计算视为数学函数的求值,避免状态变化和可变数据。函数式编程强调函数的纯粹性、不可变数据和函数的组合。
1.1.2 核心原则
不可变数据(Immutability):数据一旦创建就不能修改
纯函数(Pure Functions):函数的输出只依赖于输入,没有副作用
函数是一等公民(First-class Functions):函数可以作为参数传递,作为返回值返回,也可以赋值给变量
高阶函数(Higher-order Functions):接受函数作为参数或返回函数的函数
函数组合(Function Composition):将多个函数组合成一个新函数
1.2 不可变数据
1.2.1 概念
不可变数据是指一旦创建就不能修改的数据。在Swift中,可以使用let关键字创建不可变变量。
1.2.2 原理
不可变数据可以避免许多常见的错误,如并发问题、意外修改和状态不一致。它还可以使代码更易于推理和测试。
1.2.3 使用示例
// 不可变数组
let numbers = [1, 2, 3, 4, 5]
// 不能直接修改不可变数组
// numbers.append(6) // 编译错误
// 而是创建一个新数组
let newNumbers = numbers + [6]
print(newNumbers) // 输出: [1, 2, 3, 4, 5, 6]
// 不可变字典
let user = ["name": "Alice", "age": 25]
// 不能直接修改不可变字典
// user["email"] = "alice@example.com" // 编译错误
// 而是创建一个新字典
var mutableUser = user
mutableUser["email"] = "alice@example.com"
let newUser = mutableUser
print(newUser) // 输出: ["name": "Alice", "age": 25, "email": "alice@example.com"]
1.3 纯函数
1.3.1 概念
纯函数是指满足以下两个条件的函数:
函数的输出只依赖于输入:相同的输入总是产生相同的输出
没有副作用:函数不会修改外部状态,如全局变量、输入参数或I/O操作
1.3.2 原理
纯函数具有可预测性、可测试性和可并行性。由于纯函数没有副作用,它们可以安全地并行执行,也可以轻松地进行单元测试。
1.3.3 使用示例
// 纯函数:计算两个数的和
func add(_ a: Int, _ b: Int) -> Int {
return a + b
}
// 纯函数:计算数组的平方和
func squareSum(_ numbers: [Int]) -> Int {
return numbers.reduce(0) { $0 + $1 * $1 }
}
// 非纯函数:修改外部变量
var counter = 0
func increment() -> Int {
counter += 1 // 副作用:修改外部变量
return counter
}
// 非纯函数:修改输入参数
func appendOne(_ array: inout [Int]) {
array.append(1) // 副作用:修改输入参数
}
// 非纯函数:执行I/O操作
func printAndReturn(_ value: Int) -> Int {
print(value) // 副作用:I/O操作
return value
}
1.4 高阶函数
1.4.1 概念
高阶函数是指接受函数作为参数或返回函数的函数。
1.4.2 原理
高阶函数允许我们编写更抽象、更灵活的代码。它们可以将通用的操作(如遍历、过滤、映射)与具体的业务逻辑分离。
1.4.3 使用示例
// 接受函数作为参数的高阶函数
func applyOperation(_ operation: (Int) -> Int, to value: Int) -> Int {
return operation(value)
}
// 定义一个函数作为参数
func double(_ x: Int) -> Int {
return x * 2
}
// 使用高阶函数
let result = applyOperation(double, to: 5)
print(result) // 输出: 10
// 使用闭包作为参数(更常见)
let result2 = applyOperation({ $0 * 3 }, to: 5)
print(result2) // 输出: 15
// 返回函数的高阶函数
func makeMultiplier(_ factor: Int) -> (Int) -> Int {
return { $0 * factor }
}
// 使用返回的函数
let doubleFunction = makeMultiplier(2)
let tripleFunction = makeMultiplier(3)
print(doubleFunction(5)) // 输出: 10
print(tripleFunction(5)) // 输出: 15
2. Swift中的函数式特性
2.1 map、filter、reduce
2.1.1 map
2.1.1.1 概念
map方法用于将一个序列(如数组)中的每个元素转换为另一个元素,返回一个新的序列。
2.1.1.2 原理
map方法接受一个闭包作为参数,该闭包定义了如何将序列中的每个元素转换为新元素。map方法遍历原序列,对每个元素应用闭包,然后将结果收集到一个新序列中返回。
2.1.1.3 使用示例
// 使用map将数组中的每个元素翻倍
let numbers = [1, 2, 3, 4, 5]
let doubledNumbers = numbers.map { $0 * 2 }
print(doubledNumbers) // 输出: [2, 4, 6, 8, 10]
// 使用map将字符串数组转换为长度数组
let names = ["Alice", "Bob", "Charlie"]
let nameLengths = names.map { $0.count }
print(nameLengths) // 输出: [5, 3, 7]
// 使用map将数组中的每个元素转换为字符串
let integers = [1, 2, 3]
let strings = integers.map { "Number ($0)" }
print(strings) // 输出: ["Number 1", "Number 2", "Number 3"]
2.1.2 filter
2.1.2.1 概念
filter方法用于从序列中筛选出符合条件的元素,返回一个新的序列。
2.1.2.2 原理
filter方法接受一个闭包作为参数,该闭包定义了筛选条件。filter方法遍历原序列,对每个元素应用闭包,只有当闭包返回true时,该元素才会被包含在新序列中。
2.1.2.3 使用示例
// 使用filter筛选出偶数
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let evenNumbers = numbers.filter { $0 % 2 == 0 }
print(evenNumbers) // 输出: [2, 4, 6, 8, 10]
// 使用filter筛选出长度大于5的字符串
let names = ["Alice", "Bob", "Charlie", "David", "Eve"]
let longNames = names.filter { $0.count > 5 }
print(longNames) // 输出: ["Charlie"]
// 使用filter筛选出大于等于60的分数
let scores = [85, 92, 78, 65, 59, 98]
let passingScores = scores.filter { $0 >= 60 }
print(passingScores) // 输出: [85, 92, 78, 65, 98]
2.1.3 reduce
2.1.3.1 概念
reduce方法用于将序列中的所有元素组合成一个单一的值。
2.1.3.2 原理
reduce方法接受两个参数:一个初始值和一个闭包。闭包接受两个参数:当前累积值和序列中的下一个元素,并返回一个新的累积值。reduce方法遍历序列,对每个元素应用闭包,更新累积值,最后返回最终的累积值。
2.1.3.3 使用示例
// 使用reduce计算数组元素的和
let numbers = [1, 2, 3, 4, 5]
let sum = numbers.reduce(0, { $0 + $1 })
print(sum) // 输出: 15
// 使用reduce计算数组元素的乘积
let product = numbers.reduce(1, { $0 * $1 })
print(product) // 输出: 120
// 使用reduce将数组元素连接成一个字符串
let names = ["Alice", "Bob", "Charlie"]
let joinedNames = names.reduce("", { $0 + $1 + ", " })
// 移除末尾的逗号和空格
let formattedNames = String(joinedNames.dropLast(2))
print(formattedNames) // 输出: "Alice, Bob, Charlie"
// 使用reduce查找数组中的最大值
let values = [5, 3, 9, 1, 7]
let maxValue = values.reduce(Int.min, { max($0, $1) })
print(maxValue) // 输出: 9
2.2 flatMap与compactMap
2.2.1 flatMap
2.2.1.1 概念
flatMap方法用于将序列中的每个元素转换为一个序列,然后将所有这些序列连接成一个单一的序列。
2.2.1.2 原理
flatMap方法接受一个闭包作为参数,该闭包将每个元素转换为一个序列。flatMap方法遍历原序列,对每个元素应用闭包,得到多个序列,然后将这些序列扁平化为一个单一的序列。
2.2.1.3 使用示例
// 使用flatMap将二维数组转换为一维数组
let nestedArray = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
let flattenedArray = nestedArray.flatMap { $0 }
print(flattenedArray) // 输出: [1, 2, 3, 4, 5, 6, 7, 8, 9]
// 使用flatMap将数组中的每个元素转换为多个元素
let numbers = [1, 2, 3]
let expandedNumbers = numbers.flatMap { [ $0, $0 * 2 ] }
print(expandedNumbers) // 输出: [1, 2, 2, 4, 3, 6]
// 使用flatMap处理字符串数组
let words = ["Hello", "World"]
let characters = words.flatMap { $0 }
print(characters) // 输出: ["H", "e", "l", "l", "o", "W", "o", "r", "l", "d"]
2.2.2 compactMap
2.2.2.1 概念
compactMap方法用于将序列中的每个元素转换为可选值,然后过滤掉nil值,返回一个新的序列。
2.2.2.2 原理
compactMap方法接受一个闭包作为参数,该闭包将每个元素转换为可选值。compactMap方法遍历原序列,对每个元素应用闭包,然后过滤掉nil值,将非nil值解包后收集到一个新序列中。
2.2.2.3 使用示例
// 使用compactMap过滤掉nil值
let optionalNumbers: [Int?] = [1, nil, 3, nil, 5]
let validNumbers = optionalNumbers.compactMap { $0 }
print(validNumbers) // 输出: [1, 3, 5]
// 使用compactMap将字符串转换为整数,过滤掉无效的字符串
let stringNumbers = ["123", "abc", "456", "789", "def"]
let integers = stringNumbers.compactMap { Int($0) }
print(integers) // 输出: [123, 456, 789]
// 使用compactMap处理包含可选字符串的数组
let optionalStrings: [String?] = ["Alice", nil, "Bob", "Charlie", nil]
let validStrings = optionalStrings.compactMap { $0 }
print(validStrings) // 输出: ["Alice", "Bob", "Charlie"]
2.3 函数组合
2.3.1 概念
函数组合是将多个函数组合成一个新函数的过程。组合后的函数将第一个函数的输出作为第二个函数的输入,依此类推。
2.3.2 原理
函数组合允许我们将复杂的操作分解为简单的、可复用的函数,然后将它们组合起来。这使得代码更易于理解、测试和维护。
2.3.3 使用示例
// 定义几个简单的函数
func addOne(_ x: Int) -> Int {
return x + 1
}
func multiplyByTwo(_ x: Int) -> Int {
return x * 2
}
func square(_ x: Int) -> Int {
return x * x
}
// 手动组合函数
let result1 = square(multiplyByTwo(addOne(5)))
print(result1) // 输出: (5+1)*2=12, 12^2=144
// 定义函数组合运算符
infix operator >>>: AdditionPrecedence
func >>> <T, U, V>(lhs: @escaping (T) -> U, rhs: @escaping (U) -> V) -> (T) -> V {
return { rhs(lhs($0)) }
}
// 使用自定义运算符组合函数
let combinedFunction = addOne >>> multiplyByTwo >>> square
let result2 = combinedFunction(5)
print(result2) // 输出: 144
// 另一个函数组合示例
func toUpperCase(_ string: String) -> String {
return string.uppercased()
}
func addExclamation(_ string: String) -> String {
return string + "!"
}
let transformString = toUpperCase >>> addExclamation
let result3 = transformString("hello")
print(result3) // 输出: "HELLO!"
2.4 柯里化
2.4.1 概念
柯里化(Currying)是将接受多个参数的函数转换为一系列接受单个参数的函数的过程。
2.4.2 原理
柯里化允许我们部分应用函数,即只提供函数的部分参数,返回一个接受剩余参数的新函数。这使得函数更易于组合和复用。
2.4.3 使用示例
// 传统的多参数函数
func add(_ a: Int, _ b: Int) -> Int {
return a + b
}
// 调用传统函数
let sum1 = add(3, 5)
print(sum1) // 输出: 8
// 柯里化函数
func curriedAdd(_ a: Int) -> (Int) -> Int {
return { b in
return a + b
}
}
// 调用柯里化函数
let sum2 = curriedAdd(3)(5)
print(sum2) // 输出: 8
// 部分应用柯里化函数
let addThree = curriedAdd(3)
let sum3 = addThree(5)
print(sum3) // 输出: 8
let sum4 = addThree(10)
print(sum4) // 输出: 13
// 另一个柯里化示例
func multiply(_ a: Int) -> (Int) -> (Int) -> Int {
return { b in
return { c in
return a * b * c
}
}
}
let multiplyByTwo = multiply(2)
let multiplyByTwoAndThree = multiplyByTwo(3)
let result = multiplyByTwoAndThree(4) // 2 * 3 * 4 = 24
print(result) // 输出: 24
// 直接调用
let result2 = multiply(2)(3)(4)
print(result2) // 输出: 24
3. 函数式编程实战示例
3.1 数据转换示例
// 假设有一个用户数组
struct User {
let name: String
let age: Int
let isActive: Bool
}
let users = [
User(name: "Alice", age: 25, isActive: true),
User(name: "Bob", age: 30, isActive: false),
User(name: "Charlie", age: 35, isActive: true),
User(name: "David", age: 28, isActive: true),
User(name: "Eve", age: 22, isActive: false)
]
// 使用函数式编程处理数据
let activeUserNames = users
.filter { $0.isActive } // 筛选活跃用户
.map { $0.name } // 提取用户名
.sorted() // 排序
print(activeUserNames) // 输出: ["Alice", "Charlie", "David"]
// 计算活跃用户的平均年龄
let activeAges = users
.filter { $0.isActive } // 筛选活跃用户
.map { $0.age } // 提取年龄
let averageAge = activeAges.reduce(0, +) / activeAges.count
print(averageAge) // 输出: (25+35+28)/3 = 29
3.2 链式调用示例
// 对数组进行一系列转换
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let result = numbers
.filter { $0 % 2 == 0 } // 筛选偶数
.map { $0 * 2 } // 翻倍
.filter { $0 > 10 } // 筛选大于10的数
.reduce(0, +) // 求和
print(result) // 输出: (12+16+20) = 48
// 解释: [2,4,6,8,10] -> [4,8,12,16,20] -> [12,16,20] -> 48
4. 总结
函数式编程是一种强大的编程范式,它强调不可变数据、纯函数、高阶函数和函数组合。Swift提供了丰富的函数式编程特性,包括:
map、filter、reduce等高阶函数flatMap和compactMap用于处理复杂数据结构函数组合和柯里化
闭包和尾随闭包语法
函数式编程可以使代码更易于理解、测试和维护,同时避免许多常见的错误。它特别适合处理数据流、转换数据和构建复杂的系统。
通过掌握Swift中的函数式编程特性,你可以编写更加简洁、优雅和高效的代码。
5. 练习
使用
map将数组[1, 2, 3, 4, 5]中的每个元素转换为其平方根。使用
filter和map从数组["apple", "banana", "cherry", "date", "elderberry"]中筛选出长度大于5的水果名称,并将它们转换为大写。使用
reduce计算数组[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]中所有偶数的和。使用
flatMap将二维数组[[1, 2, 3], [4, 5], [6, 7, 8, 9]]转换为一维数组,并计算所有元素的和。定义三个函数:
addTwo(加2)、multiplyByThree(乘3)和subtractOne(减1),然后使用函数组合将它们组合成一个新函数,计算(x + 2) * 3 - 1。
通过这些练习,你可以巩固所学的函数式编程知识,提高在Swift中使用函数式编程的能力。