Swift 中存在一种奇妙的共存:初学者可以在 Playgrounds 中用一行 print("Hello") 开始学习,但同一种语言也用来编写交织着泛型和宏的标准库。面向小学生的编程教育 App(Swift Playgrounds)和编译器级别的系统代码共享同一套语法。
大多数语言会二选一:易学(Python 系)或强大(C++ 系)。Swift 宣布两者都要,而让这一目标成为可能的设计原则有一个名字:渐进式揭示(Progressive Disclosure)。
本文是 Swift 哲学系列的第 2 篇。第 1 篇讨论的 Safe、Fast、Expressive 是“要构建什么”的哲学,而 Progressive Disclosure 是“按什么顺序展示它”的哲学。
什么是渐进式揭示 — 在需要之前隐藏
Progressive Disclosure 原本是 UI 设计术语。它先展示常用功能,把高级功能隐藏在“更多”后面,避免初学者感到 overwhelmed。就像相机 App 只突出显示快门按钮,把 ISO 和快门速度隐藏在专业模式中。
Swift 团队将这一原则应用到了语言语法中,这也是官方提出的目标。Chris Lattner 曾在多次访谈中将 Swift 描述为“逐步揭示复杂性(progressive disclosure of complexity)的语言”。直到现在,Swift Evolution 提案评审仍会把“这种语法是否破坏 progressive disclosure”作为评审标准。
把这一原则浓缩成一句话,就是:
尚未学习的概念,不应出现在尚未掌握它的人的代码中。
这不只是“也有简单功能”。它提出了更强的要求:编写简单代码时,困难概念甚至不应进入视野。
Hello World 对比 — 数一数登场角色
只要对比 Hello World,就能看出这一原则实际改变了什么。
// Java (11 上一篇)
public class Main {
public static void main(String[] args) {
System.out.println("Hello, world!");
}
}
要完全理解这五行代码,需要了解类、访问控制、static、方法、数组和标准输出对象。第一天学习的代码里就有六个角色。老师只能说:“先记住,之后再解释。”
// Swift
print("Hello, world!")
Swift 只需要一行。你可以直接在文件顶层编写可执行代码,不需要分号,也不需要 import。登场角色只有一个函数调用,不需要“先记住”。
关键就在这里。Swift 同样拥有类、访问控制和 static,只是它们没有出现在这段代码中。概念并不是不存在,而是对暂时不需要它们的人不可见。
同一功能的多个层次 — 语法逐级展开
拆解 Swift 语法后,你会发现同一功能叠加在多个难度层次上。下面看看几个典型例子。
**函数 → 闭包 → 简写闭包。**以数组排序为例,刚开始只需传入一个具名函数。
func byLength(_ a: String, _ b: String) -> Bool {
a.count < b.count
}
names.sorted(by: byLength)
学会闭包后,就可以内联编写。
names.sorted(by: { a, b in a.count < b.count })
掌握简写语法后,可以缩短成这样。
names.sorted { $0.count < $1.count }
三段代码完成的事情完全相同。不懂尾随闭包和 $0,第一天也能完成排序;学会后则可以写得更简洁。学习新层次不是必需条件,而是一种奖励。
类型推断 → 显式类型。 从 let age = 30 开始,只有在需要指定精度或定义 API 边界时,类型标注才会出现。一开始不会强迫你写 let age: Int = 30。
自动成员式 init → 自定义 init。 对 struct 来说,即使不写初始化代码,编译器也会生成初始化器。等到需要初始化逻辑时,再学习 init 语法即可。
忽略错误 → try? → do-catch → 类型化 throws。 错误处理同样提供了一座可以按兴趣程度选择攀登的阶梯。
视野之外的复杂性 — 在不可见之处工作的东西
比语法阶梯更令人印象深刻的是,高级功能在幕后支撑着初学者代码。
那个 print("Hello")print 的实际声明如下。
func print(
_ items: Any...,
separator: String = " ",
terminator: String = "\n"
)
可变参数、默认参数和 Any 类型。声明中包含三个初学者尚不了解的概念,但调用方一个都不需要知道。正因为有默认参数这一高级功能,面向初学者的用法才能保持简单。高级功能不是用来增加复杂性,而是用来吸收复杂性。
字符串插值("이름: \(name)")也是同样的结构。对使用者来说,这是第一周就会学到的基础语法,但底层完整隐藏着名为 ExpressibleByStringInterpolation 协议的定制层。正是这一层让 SwiftUI 的 Text 能通过字符串插值处理图片和日期格式化。99% 的用户一辈子都能用得很好,却无需知道这个协议存在。只有库作者需要打开这扇门。
SwiftUI 是这一原则的集大成者。
struct ContentView: View {
var body: some View {
Text("Hello")
}
}
这段简短代码之下,依赖着不透明类型(some)、resultBuilder 和协议关联类型。它们都是 Swift 中最难的功能,但第一次创建 UI 的人甚至不知道它们存在,就能显示出界面。“some View 是什么?”通常几个月后才会问到,那时再学习即可。
反面案例 — 没有这一原则会发生什么
与缺少这一原则的语言进行比较,就能清楚看出它的价值。
学习 C++ 时,指针、引用、拷贝构造函数等内存概念从第一周起就暴露在语法表面。它们不是隐藏的,而是无法绕过的关卡。Rust 为了安全,要求所有用户一开始就面对所有权和生命周期。这是出色的设计,但如果以“编写第一个程序所需的概念数量”为标准,它处在完全相反的极端。
有趣的是,其他语言也朝着 Swift 的方向发展。Java 在 21 中引入了只需 void main() 即可运行的隐式类(JEP 445,Java 改进提案),C# 则加入了顶级语句。两者的理由相同:从初学者的第一段代码中移除仪式性样板。前一代语言花了十年,才追上 Swift 在 2014 年就默认采用的做法。
也有批评 — 阶梯崩塌的地方
公平地说,并不是所有评价都认为 Swift 完美遵守了这一原则。
最大的批评是,随着语言不断膨胀,中间阶梯变得越来越陡。入门很容易,但一进入生产代码或库代码,泛型约束、some 与 any 的区别、Sendable 标注就会同时出现。尤其是 Swift 6 的 strict concurrency,被批评为与 progressive disclosure 正面冲突,因为它会“在尚未学习并发的人所写的代码中显示编译错误”。甚至 Chris Lattner 也曾表示 Swift 变复杂了。
Swift 团队也意识到了这种张力。Swift 6.1 之后的多项提案(默认 actor 隔离选项、无名 main 等)表明,它们正在朝着“在遇到并发概念之前,不要让用户遇到这些概念”的方向整理。更准确地说,这一原则并未完成,而是一项团队至今仍在努力维护的价值。
给实践者的启示 — 作为 API 设计标准
这一原则不只适用于语言用户,也可以直接应用于我们每天创建的函数和模块,是一项设计标准。
用默认参数让常见情况免费可用。 就像 print 的 separator,把 90% 的调用者不关心的选项隐藏在默认值后面。可以将它与要求完整配置对象的 API 进行比较。
提供一个简单的入口,把高级重载放到后面。 URLSession 是一个很好的例子。data(from: url) 可以从一行开始,而需要代理和配置的人可以使用单独开放的那一层。
如果陌生概念泄漏到调用处,就是一个设计信号。 如果使用我的库需要阅读泛型签名,就说明本应被吸收的复杂性正在泄漏。就像 print 使用可变参数却仍保持调用处简单一样,复杂性应该由声明端吞下。
总而言之,好的 API 不是功能少的 API,而是让尚未学习的功能不可见的 API。
总结
- Progressive Disclosure 是 Swift 的官方设计原则:“让尚未学习的概念不出现在代码中”。
- 一行 print 的 Hello World、类型推断、自动 init 和闭包简写阶梯,都是这一原则的产物。
- 高级功能(默认参数、resultBuilder、字符串插值协议)都是按照吸收初学者代码复杂性的方向设计的。
- 也存在原则动摇的地方,例如 Swift 6 的并发;语言团队至今仍在修复这座阶梯。
- 我们可以将同样的标准应用到自己的 API:常见用法保持一行完成,让声明端吸收复杂性。
下一篇是第三个哲学故事。Swift 标准库为什么几乎全部由 struct 构成?本文将讨论值类型优先原则。

![[Swift 哲学 #2] 什么是 Progressive Disclosure? 封面图](/assets/images/posts/290b697a-f011-4cb7-8331-0a439b666fcd/1.jpg)