1843 Ada Lovelace 第一個程式#
案發現場#
時間回到 1843 年的倫敦。工業革命的蒸汽機轟鳴作響,紡織廠裡的雅卡爾織布機(Jacquard Loom)用打孔卡片控制著繁複的花紋。而在數學的世界裡,一個巨大的未解之謎正困擾著科學家:人類能不能造出一台自動做數學的機器?
當時的數學家面臨的困境非常實際:航海需要對數表與三角函數表,天文觀測需要長達數百頁的函數值表,而這些表格全靠人工計算、人工抄寫,錯誤百出。數學家 Charles Babbage 就曾憤怒地說:「我真希望這些計算是用蒸汽完成的!」於是他設計了兩台機器:先是「差分機」(Difference Engine,用來自動計算多項式表),後來是更宏大的「分析機」(Analytical Engine)。
分析機的設計驚人地完整:它有「儲存庫」(Store,相當於今天的記憶體)、「運算廠」(Mill,相當於今天的 CPU)、用打孔卡片輸入指令與資料,甚至有自動列印輸出。它是一台通用型的計算機器——不是只會算某一種東西,而是可以依照指令做任何代數運算。
但問題來了:機器設計出來了(雖然始終沒被完整建造),該如何「告訴」機器要做什麼? 沒有人知道「程式設計」這件事該怎麼做,甚至連「程式」這個概念都不存在。這就是案發現場:一個尚未誕生的學科,正在等待第一個開路的人。
而這個人,是數學家拜倫勳爵的女兒——Ada Lovelace(愛達·勒芙蕾絲)。她在 17 歲時見到 Babbage 的差分機模型,深深著迷。1843 年,她翻譯了義大利軍事工程師 Menabrea 關於分析機的法文論文,並加上了自己的大量註解(Notes A 到 G)。就是其中的 Note G,寫下了人類歷史上第一個程式。
偵查過程#
Ada 的偵查思路,可以分為三層推理。
第一層:選一個夠難的題目。 Ada 沒有選簡單的加法或查表,而是選了「伯努利數」(Bernoulli Numbers)的計算。伯努利數是數學分析中極重要的數列,出現在 \(\tan x\) 的泰勒展開式中:
這些係數正是伯努利數 \(B_1, B_2, B_3, \ldots\) 的組合。伯努利數可以由遞迴關係算出。從 Bernoulli 方程的生成關係可以推導:
整理後可得遞迴公式:
注意這個公式的關鍵:計算 \(B_n\) 需要先用到所有的 \(B_0, B_1, \ldots, B_{n-1}\) 。也就是說,這不是單純的重複動作,而是一個動態規劃式的巢狀計算:外層控制「現在要算第幾個數」,內層要對所有先前算好的數做組合數加權求和。選這個題目,Ada 等於是在展示分析機能處理「有依賴關係的多層次計算」,而不只是無腦重複。這是第一個偵查洞察:用最難的例子來證明機器的通用性。
第二層:把數學公式分解成機器操作。 Babbage 的分析機只有幾種基本操作:加、減、乘、除、資料搬移。Ada 做的事,是現代編譯器的雛形——把高階的數學表達式人工編譯成一連串基本操作。例如計算
她分解成一連串的操作序列,並用一個大表格(一張巨大的流程圖前身)追蹤每個變數在每一步的值。表格的欄位包括操作編號、作用中的變數、參數行(哪一欄的資料要送進去)、結果行(結果要存到哪一欄)。這正是今日組合語言的樣貌:操作碼 + 運算元 + 目的位址。
在 Ada 的表格中,一次「除法」被表示為(簡化自 Note G 原表):
操作 0,1,2,3:
V1 ← V2 × V2 (計算組合數的分子部分)
V3 ← V1 / V4 (除以階乘)
V5 ← V3 × V6 (乘上之前的伯努利數)
V7 ← V5 + V8 (累加)
第三層:發明「循環」與「分支」。 這是整個案件最精彩的部分。分析機的打孔卡片機制借自織布機,Babbage 設計了「補償卡片」(backing card),可以讓讀卡機倒回去重新讀取前面的一串卡片。Ada 敏銳地意識到這個機制的深層意義:這意味著同一組指令可以被重複執行,而不需要重複打出同樣的卡片。她寫道:
「這個循環操作可以在計算中重複任意次,循環的次數也可以由不同的條件決定。」
這段話,用今天的語言說,就是發明了 loop(迴圈)。而且她進一步指出,循環的次數可以依據計算過程中的狀態而改變——這已經觸及了 conditional branch(條件分支) 的概念。用虛擬碼重寫 Ada 在 Note G 中的邏輯,大致是:
n = 1
loop:
# 內層:對 k = 0..n-1 累加 C(n+1, k) * B[k]
sum = 0
for k in 0..n-1:
sum = sum + comb(n+1, k) * B[k]
B[n] = -sum / (n+1)
n = n + 1
# 依條件決定是否繼續循環(補償卡片倒轉)
if n <= N: goto loop
偵查到此,真相浮現:Ada 不只是把公式翻譯成機器操作,她發明了變數追蹤表、迴圈、條件執行、巢狀計算——現代程式設計的四大基石,她一次全展示了。這是歷史上第一個完整、可執行(若機器建成)、有巢狀迴圈的程式。
最後一層推理,也是 Ada 最超越時代的洞見:她在 Note A 中寫下震撼後世的一段話:
「分析機編織代數圖案,就像雅卡爾織布機編織花朵與葉子。」
並且她斷言,分析機的符號不只代表數量,可以代表任何有規則對應的東西——音符、邏輯、文字。換句話說,她預見了電腦不只會算數字,還能處理任意符號、甚至作曲。這就是她所說的「詩意的科學」(poetical science):數學的嚴謹配上想像力的翅膀。這個洞見直到一百年後圖靈提出通用圖靈機、電腦真的開始處理文字與邏輯時,才被完全證實。
結案報告#
Ada Lovelace 於 1852 年因癌症去世,年僅 36 歲。分析機終其一生(以及 Babbage 的餘生)都沒有被完整建造出來。她的註解文章在維多利亞時代幾乎無人聞問,埋沒了將近一百年。
但她的遺產最終震撼了世界:
- 第一個程式:Note G 的伯努利數程式,被公認為史上第一個電腦程式。美國國防部在 1980 年代開發的 Ada 語言,就是以她的名字命名。
- 程式設計作為一門獨立的技藝:她第一個意識到「寫程式」和「造機器」是兩件不同的事。Babbage 是硬體工程師,Ada 是世界上第一位軟體工程師。
- 迴圈與分支概念:她在 1843 年就闡述的循環與條件執行,成為所有後續語言的基本控制結構——從 1957-FORTRAN.md 的 DO 迴圈、1958-LISP.md 的條件表達式,到今天所有語言的
for、while、if。 - 符號處理的預言:「電腦不只算數字」的洞見,直接預言了1958-LISP.md(符號處理與 AI)的誕生,以及今天的自然語言處理與人工智慧。
- Ada Lovelace 獎章:英國電腦學會(BCS)每年頒發以她命名的獎章,表彰對電腦科學的卓越貢獻。
偵探故事的最後一個轉折:有人質疑 Ada 只是「翻譯並註解」了 Menabrea 的論文,程式可能是 Babbage 口述的。但歷史學家的考證顯示,Note G 中的深度理解、對未來應用的預見,遠超 Babbage 的視野——Babbage 只想造一台算數的機器,而 Ada 看到了一台編織「任何符號圖案」的機器。第一個程式的作者,當之無愧。
證據與工具#
證據一:Note G 程式的現代重現(Python)
以下用 Python 完整重現 Ada 在 Note G 中描述的伯努利數計算演算法,包含她發明的「外層循環 + 內層累加」結構:
from math import comb
def bernoulli_numbers(N):
"""重現 Ada Lovelace Note G (1843) 的演算法"""
B = [None] * (N + 1)
B[0] = 1
for n in range(1, N + 1): # 外層循環:Ada 的「補償卡片」迴圈
s = 0.0
for k in range(0, n): # 內層循環:對所有先前的 B[k] 累加
s += comb(n + 1, k) * B[k]
B[n] = -s / (n + 1) # 遞迴公式 B_n = -1/(n+1) * sum
return B
print(bernoulli_numbers(8))
# [1, -0.5, 0.16666666666666666, 0.0, -0.03333333333333333,
# 0.0, 0.023809523809523808, 0.0, -0.03333333333333333]
證據二:模擬 Ada 的「變數追蹤表」
Ada 用一張大表格記錄每一步操作後所有變數的值——這正是現代除錯器的原型。以下模擬這張表:
def trace_table(N):
"""模擬 Ada 的變數追蹤表(Trace)"""
B = {0: 1.0}
log = []
for n in range(1, N + 1):
s = 0.0
for k in range(0, n):
s += comb(n + 1, k) * B[k]
log.append((f"op{k}", f"sum += C({n+1},{k}) * B[{k}]", dict(B), s))
B[n] = -s / (n + 1)
log.append((f"op{n}", f"B[{n}] = -sum/{n+1}", dict(B), None))
return log
for op, desc, state, val in trace_table(3):
print(f"{op:5s} {desc:30s} state={state}")
證據三:驗證伯努利數出現在 \(\tan x\) 展開式中
import math
def tan_taylor(x, B):
"""用伯努利數重建 tan x 的泰勒級數(驗證 Ada 的洞察)"""
result = 0.0
for n in range(1, len(B)):
c = (2 ** (2*n) * (2 ** (2*n) - 1) * B[n]) / math.factorial(2*n)
result += c * x ** (2*n - 1)
return result
B = [1, -0.5, 1/6, 0, -1/30, 0, 1/42, 0, -1/30]
x = 0.5
print(tan_taylor(x, B)) # 約 0.5463
print(math.tan(x)) # 0.5463 ✓
三份證據互相印證:Ada 在 1843 年寫下的演算法完全正確,而且她選擇的伯努利數問題,正是連接「數列遞迴」「動態規劃」「級數展開」的完美試金石。一個多世紀後,這些思想化為 FORTRAN 的迴圈、LISP 的符號處理,以及整個軟體產業。