Swift 中的函数式编程:高级水平

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

Swift 中的函数式编程:高级水平

\n\n

函数式编程(FP)是一种将计算视为数学函数求值,并避免状态改变和可变数据的编程范式。在 Swift 这种多范式语言中,函数式特性不仅得到支持,而且深度集成到语言和标准库中。如果你已经掌握了诸如 map、filter 和 reduce 等基本概念,那么是时候深入学习高级技术了,这些技术将帮助你为 iOS 和 macOS 应用编写更安全、更可预测且更易测试的代码。在本文中,我们将通过实际示例探讨函子、单子、惰性求值和函数组合。

\n\n

1. 函子:可转换的容器

\n\n

函子是一种实现了 fmap 函数(在 Swift 中为 map)的数据类型。函子的本质是一个容器,其内容可以通过传入的函数进行转换,而无需离开容器的范围。经典的例子是 Optional。

\n\n

让我们看看函子如何以 Optional 为例工作:

\n\n
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 结构。

\n\n

Swift 中另一个重要的函子是 Array。数组上的 map 将函数应用于每个元素并返回一个新数组:

\n\n
let numbers = [1, 2, 3, 4]\nlet doubled = numbers.map { $0 * 2 }\nprint(doubled) // [2, 4, 6, 8]
\n\n

创建你自己的函子。你可以通过定义 map 方法为任何自定义类型实现函子。例如,一个简单的日志包装器:

\n\n
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\n

2. 单子:带上下文的计算链

\n\n

单子是一种函子,它额外实现了 flatMap(或 bind)方法。如果说 map 允许应用一个返回普通值的函数,那么 flatMap 则允许应用一个自身返回相同上下文中值的函数(例如,另一个 Optional)。这是构建保持上下文的操作链的关键工具。

\n\n

最直观的例子是处理可选值。假设我们有一个从字符串中提取数字的函数,以及一个将一个数除以另一个数的函数,但两者都可能返回 nil:

\n\n
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                

博客

书籍推荐