1958 - McCarthy 的 LISP(遞迴與符號計算的語言)#
案件摘要#
1958 年,John McCarthy(1927–2011) 在 MIT 發明了 LISP(LISt Processor):
LISP 的核心只有七個函數與兩個特殊形式:
一切從「串列」開始:
(DEFUN FACT (N) (COND ((EQ N 0) 1) (T (TIMES N (FACT (SUB1 N))))))
前因 -- 為什麼會有這個案子#
- McCarthy 的背景(1955–1958): McCarthy 是普林斯頓的數學博士(Post 的學生), 1955 年在達特茅斯會議(Dartmouth Conference)上發明了「人工智慧」這個詞:
1958 年他到 MIT,與 Marvin Minsky 共同創立 AI 實驗室。 - FORTRAN 的限制(1957): McCarthy 原本想用 FORTRAN 寫 AI 程式,但發現: - FORTRAN 不支援遞迴; - FORTRAN 只能處理數字(不能處理符號與串列)。
- IPL 的先例(1955–1956): Newell、Simon、Shaw 在 RAND 發明了 IPL(Information Processing Language)—— 第一個串列處理語言(1975 年兩人獲圖靈獎)——但 IPL 的語法很難用。
- 7 個函數的極簡主義: McCarthy 的目標:用最少的原語(primitives)表達一切——
這是數學的極簡主義(對照 Peano 公理的極簡風格)。
線索與推理 -- 數學式、程式、理論#
第一條線索:S-運算式與「程式 = 資料」#
S-運算式(symbolic expression):LISP 的統一語法:
關鍵:程式與資料用同一種語法——
(+ 1 2) ; 程式(求值後 = 3)
'(+ 1 2) ; 資料(串列 (+ 1 2))
- eval/apply 的循環:LISP 的核心:
- LISP 用 LISP 寫(McCarthy 1958 的奇蹟)——自舉(bootstrapping)。
第二條線索:遞迴與 lambda#
遞迴:函數呼叫自己——
lambda(匿名函數):
McCarthy 把 Church 的 λ 演算帶進了程式語言—— 函數式程式設計的起源。 - 高階函數:函數是「第一等公民」(可當參數、可當返回值)—— map、filter、reduce 的原型(LISP 的 mapcar)。
第三條線索:垃圾回收(GC)的發明#
問題:串列不斷被 cons 創造、被丟棄——記憶體怎麼辦? McCarthy 的發明(1959/1960):垃圾回收——
標記-清除法(mark-and-sweep): 1. 標記:從根部(stack、暫存器)可達的物件全部標記; 2. 清除:未標記的物件回收。
現代 GC:Java(1995)、C#(2000)、Go(2009)、Python—— 全部用 McCarthy 的思想(分代 GC、增量 GC)。
Python:LISP 核心的 Python 實現#
# 用 Python 實現一個微型 LISP(eval/apply 循環)
def tokenize(s):
"""詞法:切成記號"""
return s.replace('(', ' ( ').replace(')', ' ) ').split()
def parse(tokens):
"""語法:建 S-運算式樹"""
if len(tokens) == 0:
raise SyntaxError("unexpected EOF")
tok = tokens.pop(0)
if tok == '(':
lst = []
while tokens[0] != ')':
lst.append(parse(tokens))
tokens.pop(0) # 吃掉 ')'
return lst
elif tok == ')':
raise SyntaxError("unexpected )")
else:
return atom(tok)
def atom(tok):
"""數字或符號"""
try:
return int(tok)
except ValueError:
try:
return float(tok)
except ValueError:
return Symbol(tok)
class Symbol(str):
pass
# 環境:變數與函數
import operator as op
def standard_env():
env = {
'+': op.add, '-': op.sub, '*': op.mul, '/': op.truediv,
'>': op.gt, '<': op.lt, '>=': op.ge, '<=': op.le,
'=': op.eq,
'car': lambda x: x[0],
'cdr': lambda x: x[1:],
'cons': lambda x, y: [x] + y,
'atom': lambda x: isinstance(x, Symbol),
'len': len,
}
return env
global_env = standard_env()
def evaluate(x, env=global_env):
"""eval/apply 循環(LISP 的心臟)"""
if isinstance(x, Symbol):
return env[x] # 變數查表
elif isinstance(x, (int, float)):
return x # 常數
elif x[0] == 'if':
(_, test, conseq, alt) = x
return evaluate(conseq if evaluate(test, env) else alt, env)
elif x[0] == 'lambda':
(_, params, body) = x
def closure(*args, _params=params, _body=body, _env=env):
local = dict(zip(_params, args))
local.update(_env)
return evaluate(_body, local)
return closure
elif x[0] == 'quote':
return x[1] # 引號:不求值
elif x[0] == 'define':
(_, name, exp) = x
env[name] = evaluate(exp, env)
else:
proc = evaluate(x[0], env)
args = [evaluate(arg, env) for arg in x[1:]]
return proc(*args) # apply
# 測試:微型 LISP
print("微型 LISP(eval/apply 循環):")
print(f" (+ 1 2) = {evaluate(parse(tokenize('(+ 1 2)')))}")
print(f" (* 3 (+ 2 4)) = {evaluate(parse(tokenize('(* 3 (+ 2 4))')))}")
print(f" (car (cons 1 2)) = {evaluate(parse(tokenize('(car (cons 1 (quote (2))))')))}")
print(f" (cons 1 (cons 2 nil)) = {evaluate(parse(tokenize('(cons 1 (cons 2 (quote ())))')))}")
# 定義遞迴函數:階乘
program = """
(define fact (lambda (n) (if (= n 0) 1 (* n (fact (- n 1))))))
(fact 5)
"""
tokens = tokenize(program)
result = None
while tokens:
result = evaluate(parse(tokens))
print(f"\n遞迴階乘:(fact 5) = {result}")
# 垃圾回收的概念示範(Python 自帶 GC)
import gc
print("\n垃圾回收(McCarthy 1959 的發明):")
class Node:
def __init__(self, name):
self.name = name
self.ref = None
def __repr__(self):
return f"Node({self.name})"
# 創建一個循環引用(GC 的難題)
a = Node("a"); b = Node("b")
a.ref = b; b.ref = a # 循環引用
del a, b # 刪除外部引用
collected = gc.collect() # GC 應回收循環垃圾
print(f" 循環引用 + 外部刪除 → gc.collect() 回收 {collected} 個物件")
print(" → 標記-清除法可以處理循環引用(引用計數不能)")
print(" → Python、Java、Go 的 GC 都是 McCarthy 思想的後代")
微型 LISP(eval/apply 循環):
(+ 1 2) = 3
(* 3 (+ 2 4)) = 18
(car (cons 1 2)) = 1
(cons 1 (cons 2 nil)) = [1, 2]
遞迴階乘:(fact 5) = 120
垃圾回收(McCarthy 1959 的發明):
循環引用 + 外部刪除 → gc.collect() 回收 2 個物件
→ 標記-清除法可以處理循環引用(引用計數不能)
→ Python、Java、Go 的 GC 都是 McCarthy 思想的後代
結案 -- 後果與影響#
- AI 的第一個工具(1958–):
- Logic Theorist(1956)、GPS(1957)(Newell–Simon,1975 圖靈獎);
- ELIZA(1966)(Weizenbaum)——第一個聊天機器人;
- SHRDLU(1970)(Winograd)——自然語言理解;
- 專家系統(1980s)(Feigenbaum,1995 圖靈獎)——LISP 機器(Symbolics,1980s)的時代。
- 程式語言的深遠影響(1958–):
| LISP 的發明 | 後代 |
|---|---|
| 遞迴 | 所有高階語言(C、Java、Python) |
| lambda | Python lambda、JS arrow、Java lambda |
| 垃圾回收 | Java、C#、Go、Python |
| 條件運算式 | if/else、三元運算子 |
| 高階函數 | map/filter/reduce、串流 API |
| 巨集(macro) | Rust macro、C preprocessor |
| REPL | Python、Node、Ruby 的互動環境 |
| - Scheme(1975)與 Common Lisp(1984): | |
| - Sussman 與 Steele 的 Scheme(1975)——極簡的 LISP 方言; | |
| - 《計算機程式的構造和解釋》(SICP, 1985)——MIT 的教科書,用 Scheme—— | |
| 程式設計教育的聖經; | |
| - Common Lisp(1984):標準化的 LISP。 | |
| - Haskell 與函數式的復興(1990–): |
- 類型系統:ML 的 Hindley–Milner 型別推論——現代語言的靈感;
- React(2013):JS 的「UI = f(state)」——函數式思想;
- Rust(2015):所有權系統 + 函數式。
- 垃圾回收的世紀(1959–):
沒有 GC,就沒有「安全的」高階語言—— C 的手動記憶體管理(malloc/free)是萬惡之源(緩衝區溢位、記憶體洩漏)。 - AI 的工具與思想(1958–): - McCarthy 1955 年發明「人工智慧」這個詞; - 1971 年圖靈獎的演講:《電腦作為符號計算的機器》; - 情境感知(situated AI,1990s)、常識知識庫(Cyc, 1984–)—— McCarthy 晚年的工作(常識推理)。 - 歷史定位:McCarthy 是「AI 的命名者」與「符號計算的工程師」——
現代程式語言的一半基因來自 LISP。
關鍵人物與文獻#
- J. McCarthy:Recursive Functions of Symbolic Expressions(1960);LISP(1958);「人工智慧」(1955);1971 圖靈獎。
- P. Graham:Hackers and Painters(2004)——LISP 的商業應用(Viaweb)。
- A. Church:λ 演算(1936)——LISP 的理論基礎。
- H. Sussman & G. Steele:Scheme(1975);SICP(1985)。
- A. Newell & H. Simon:IPL(1955);1975 圖靈獎——AI 的先驅。
- 相關案件:
1957-巴克斯FORTRAN.md、1936-圖靈機.md、2018-深度學習三巨頭.md、程式語言/1958-LISP.md。