Swift 存在一種奇妙的共存:初學者能在 Playgrounds 用一行 print("Hello") 開始學習,但同一種語言也用來撰寫交織著泛型與巨集的標準函式庫。小學生的程式設計教育 App(Swift Playgrounds)與編譯器層級的系統程式碼,使用的是同一套語法。
一般語言通常二選一:容易學習(Python 系)或功能強大(C++ 系)。Swift 宣告兩者都要,而讓這件事成為可能的設計原則有一個名字:漸進式揭露(Progressive Disclosure)。
這是 Swift 哲學系列的第 2 篇。第 1 篇介紹的 Safe、Fast、Expressive,是「要打造什麼」的哲學;Progressive Disclosure 則是「要按照什麼順序呈現」的哲學。
什麼是漸進式揭露 — 在需要之前先隱藏
Progressive Disclosure 原本是 UI 設計術語。先顯示常用功能,把進階功能藏在「查看更多」後面,避免初學者被大量資訊壓倒。就像相機 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。
**自動 memberwise 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)可以從一行開始,而需要 delegate 與設定的人,則有另一個層次可用。
**如果陌生概念滲入呼叫端,就是設計訊號。**如果使用我的函式庫必須閱讀泛型簽章,就表示原本應被吸收的複雜度正在洩漏。就像 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)