7.2 同像性(Homoiconicity):代碼即資料、資料即代碼的極致對稱#
一個定義與一個深淵#
同像性:語言的程式表示與資料表示是同一種結構。在 Lisp 中:
(+ 1 2) ; 這是一個算式——執行它得到 3
'(+ 1 2) ; 加引號——它是一個「列表」,是資料
(eval '(+ 1 2)) ; 把資料當程式執行——又得到 3
quote(')與 eval 之間的往返,就是「代碼 ⇄ 資料」的雙向門。這正是哥德爾編號與圖靈通用機的思想在語言層面的復刻:程式是資料,資料可以是程式。本書從第 2 章到這裡的線索,在此閉合成一個日常工具。
巨集(Macro):在編譯期寫「寫程式的程式」#
同像性最強大的兌現是巨集:接收代碼(列表)、返回代碼(列表)的函數,在編譯期執行。
(defmacro unless (condition body)
`(if (not ,condition) ,body nil))
用戶可以用語言自身擴充語言——新增控制結構、領域特定語法(DSL),如同 Lisp 的語法本身「不存在」、一切都是庫。對比:C 的預處理器宏只是文本替換(無資料結構、易出錯),Lisp 宏操作的是真正的語法樹——元程式設計的正確形態。
現代繼承者:
- Rust 的
macro_rules!與過程宏(serde 自動生成序列化代碼) - Julia(2012):宏 + 多重派發,科學計算的「Lisp 血統復興」
- Elixir:建立在 Erlang VM 上的 Lisp 式宏語言
- Clojure(2007):Lisp 重返 JVM 與雲端——同像性在分散式資料處理中找到了新舞台
同像性的兩面#
| 優勢 | 代價 |
|---|---|
| 語言可無限擴充(DSL、新語法) | 風格碎片化——每個專案的「方言」不同 |
| 編譯期計算(零執行期成本) | 錯誤訊息更難定位(展開後的代碼) |
| 代碼轉換、生成、分析的統一模型 | 工具鏈(IDE、補全、重構)難以支援任意宏 |
| 教育價值:語言不再是黑盒 | 工業界的可維護性焦慮 |
「語言是可塑材料」與「語言是團隊契約」的張力(7.1 結尾)在同像性上達到極端。大型團隊最終往往自縛手腳(限制宏的使用),而小團隊與研究者則把它當超能力——抽象能力的分配,本身就是組織政治。
歷史支流:同像性思想的其他化身#
- Smalltalk:反射 API(物件在執行期查詢與修改自身結構)——同像性的物件導向版本
- Forth(1970):字典驅動、代碼即詞彙表——嵌入式世界的極簡同像
- JavaScript 的
eval:弱化版(JS 選了「函數是一等公民」而非「代碼是列表」) - SQL 動態拼接 → ORM → 查詢構建器:資料領域的受限同像
- LLM 時代:prompt 中嵌入代碼、代碼中生成 prompt——自然語言與程式碼的互指,是同像性的第三形態(第 16 章展開)
文化迴響:「Lisp 的詛咒」#
1980s 有句名言:「Lisp 程式員知道一切語言的價值,並拒絕使用它們。」背後是 Lisp 社群的特殊文化:
- MIT AI 實驗室的駭客倫理(Levy 的《駭客》1984 記錄的「資訊應當自由」)從這裡發源,流向自由軟體運動(Richard Stallman 的 Emacs 與 GNU,1983——駭客文化→開源運動的制度化)
- SICP(1985)把「計算 = 組合、抽象、解釋」變成一代人的世界觀
- Lisp 機器商業崩潰(1987–90)與 AI 寒冬,讓 Lisp 學界離散——學問以「特性」的形式散落進了 Java、Python、JavaScript、Clojure
「語言死了,思想永生」——這是程式語言史最反覆出現的悼詞,Lisp 是它的第一位主角。
小結#
| 概念 | 內容 | 深層對應 |
|---|---|---|
| 同像性 | 代碼=資料(S 表示式) | 哥德爾編號的日常化 |
| quote/eval | 資料⇄程式的雙向門 | 通用圖靈機的語言級實現 |
| 巨集 | 代碼轉換代碼 | 元程式設計的正確形態 |
| DSL | 用語言擴語言 | 領域知識的語法化 |