引言:当通用性成为敌人
\n\n设想你正在为网络配置、日志解析或业务规则描述编写代码。每次都要面对重复的结构、大量的括号、嵌套的条件语句,以及他人代码的痛苦阅读。感觉很熟悉吧?
\n\n在这种情况下,领域特定语言(DSL) 就能派上用场。这不是魔法,也不是重新发明轮子——而是一种让代码可读性达到自我文档化程度的方法。在本文中,我们将详细讲解如何在 Swift 中创建你自己的 DSL——强大、优雅,且无需编译器。
\n\nSwift 中的 DSL 不仅是一种时髦的 iOS 开发潮流。它是一种用于 SwiftUI、Combine,甚至后端库中的工具。你将学习设计能够解决特定任务的语言:从数据解析到 UI 描述。让我们开始吧!
\n\n什么是 DSL?为什么开发者需要它?
\n\n领域特定语言 是一种针对单一狭窄领域量身定制的语言。与通用语言(如 Swift 或 Python)不同,DSL 使用你所在领域术语的语言。例如,与其写:
\n\nlet config = NetworkConfig()\nconfig.host = "api.example.com"\nconfig.port = 443\nconfig.timeout = 30\n\n不如写:
\n\nlet config = networkConfig {\n host("api.example.com")\n port(443)\n timeout(30)\n}\n\n第二种写法读起来像是人类语言的指令。这就是 DSL 的目标——减少认知负荷,让代码实现自我文档化。
\n\n在 Swift 中,创建 DSL 最常使用:
\n- \n
- Result Builders——Swift 5.4+ 的新特性,允许使用声明式语法构建嵌套结构。 \n
- 函数式链——经典的构建器模式。 \n
- 运算符——运算符重载以创建流畅接口。 \n
但让我们直接进入正题。我们将创建一个用于解析 CSV 文件的 DSL——这是一项每个开发者一生中至少会遇到一次的任务。
\n\n示例 1:一个简单的网络配置 DSL
\n\n让我们从一个演示机制的基本示例开始。假设我们需要描述 HTTP 请求。与其使用一堆参数,不如使用一个简洁的 DSL。
\n\n// Define the request structure\nstruct HTTPRequest {\n var method: String = "GET"\n var path: String = "/"\n var headers: [String: String] = [:]\n var body: Data? = nil\n}\n\n// Create a DSL using Result Builder\n@resultBuilder\nstruct RequestBuilder {\n static func buildBlock(_ components: HTTPRequestModifier...) -> HTTPRequest {\n var request = HTTPRequest()\n for mod in components {\n mod.apply(&request)\n }\n return request\n }\n}\n\n// Request modifiers\nprotocol HTTPRequestModifier {\n func apply(_ request: inout HTTPRequest)\n}\n\nstruct MethodModifier: HTTPRequestModifier {\n let method: String\n func apply(_ request: inout HTTPRequest) {\n request.method = method\n }\n}\n\nstruct PathModifier: HTTPRequestModifier {\n let path: String\n func apply(_ request: inout HTTPRequest) {\n request.path = path\n }\n}\n\n// Helper functions\nfunc method(_ m: String) -> HTTPRequestModifier { MethodModifier(method: m) }\nfunc path(_ p: String) -> HTTPRequestModifier { PathModifier(path: p) }\n\n// Usage\n@RequestBuilder\nfunc buildGetRequest() -> HTTPRequest {\n method("GET")\n path("/users")\n}\n\nlet request = buildGetRequest()\nprint(request.method, request.path) // GET /users\n\n这段代码是 Swift 中任何 DSL 的基础。@resultBuilder 收集块内的所有表达式,并将它们转换为单一结构。重要提示:每个元素都是一个修改状态的操作器。
现在让我们将任务复杂化:添加嵌套和条件。