Swift 中的函数式编程:高级水平
\n\n函数式编程(FP)是一种将计算视为数学函数求值,并避免状态改变和可变数据的编程范式。在 Swift 这种多范式语言中,函数式特性不仅得到支持,而且深度集成到语言和标准库中。如果你已经掌握了诸如 map、filter 和 reduce 等基本概念,那么是时候深入学习高级技术了,这些技术将帮助你为 iOS 和 macOS 应用编写更安全、更可预测且更易测试的代码。在本文中,我们将通过实际示例探讨函子、单子、惰性求值和函数组合。
1. 函子:可转换的容器
\n\n函子是一种实现了 fmap 函数(在 Swift 中为 map)的数据类型。函子的本质是一个容器,其内容可以通过传入的函数进行转换,而无需离开容器的范围。经典的例子是 Optional。
让我们看看函子如何以 Optional 为例工作:
let maybeNumber: Int? = 5\nlet result = maybeNumber.map { $0 * 2 }\nprint(result) // Optional(10)\n\nlet nilValue: Int? = nil\nlet nilResult = nilValue.map { $0 * 2 }\nprint(nilResult) // nil\n\n这里,Optional 就是一个函子。如果内部有值,map 会应用闭包并返回一个新的 Optional。如果没有值,则操作不会执行,我们得到 nil。这避免了冗长的 if let 结构。
Swift 中另一个重要的函子是 Array。数组上的 map 将函数应用于每个元素并返回一个新数组:
let numbers = [1, 2, 3, 4]\nlet doubled = numbers.map { $0 * 2 }\nprint(doubled) // [2, 4, 6, 8]\n\n创建你自己的函子。你可以通过定义 map 方法为任何自定义类型实现函子。例如,一个简单的日志包装器:
struct Logged<T> {\n let value: T\n let log: [String]\n \n func map<U>(_ transform: (T) -> U) -> Logged<U> {\n let newValue = transform(value)\n return Logged<U>(value: newValue, log: log + ["Transformed \\(value) to \\(newValue)"])\n }\n}\n\nlet logged = Logged(value: 10, log: ["Initial value"])\nlet transformed = logged.map { $0 + 5 }\nprint(transformed.log) // ["Initial value", "Transformed 10 to 15"]\n\n2. 单子:带上下文的计算链
\n\n单子是一种函子,它额外实现了 flatMap(或 bind)方法。如果说 map 允许应用一个返回普通值的函数,那么 flatMap 则允许应用一个自身返回相同上下文中值的函数(例如,另一个 Optional)。这是构建保持上下文的操作链的关键工具。
最直观的例子是处理可选值。假设我们有一个从字符串中提取数字的函数,以及一个将一个数除以另一个数的函数,但两者都可能返回 nil:
func parseNumber(_ string: String) -> Int? {\n return Int(string)\n}\n\nfunc divideSafely(_ a: Int, by b: Int) -> Int? {\n guard b != 0 else { return nil }\n return a / b\n}\n\n// 不使用 flatMap 时,必须使用嵌套的 if let\nif let num1 = parseNumber("10") {\n if let result = divideSafely(num1, by: 2) {\n print(result) // 5\n }\n}\n\n// 使用 flatMap 后一切变得更优雅\nlet result = parseNumber("10").flatMap { divideSafely($0, by: 2) }\nprint(result) // Optional(5)\n\nlet failed = parseNumber("abc").flatM