跳轉到

1958 - McCarthy 的 LISP(遞迴與符號計算的語言)#

案件摘要#

1958 年,John McCarthy(1927–2011) 在 MIT 發明了 LISP(LISt Processor):

\[ \boxed{\text{程式 = 資料(S-運算式的統一)}} \]

LISP 的核心只有七個函數與兩個特殊形式:

\[ \text{car, cdr, cons, atom, eq, eq, cond, lambda}. \]

一切從「串列」開始:

(DEFUN FACT (N) (COND ((EQ N 0) 1) (T (TIMES N (FACT (SUB1 N))))))
LISP 的三大發明: 1. 遞迴作為主要的控制結構(FORTRAN 當時不支援遞迴); 2. 垃圾回收(garbage collection)——McCarthy 發明; 3. 符號計算——AI 的第一個工具。 1971 年,McCarthy 獲得圖靈獎——「人工智慧」這個詞也是他發明的(1955)。

前因 -- 為什麼會有這個案子#

  • McCarthy 的背景(1955–1958): McCarthy 是普林斯頓的數學博士(Post 的學生), 1955 年在達特茅斯會議(Dartmouth Conference)上發明了「人工智慧」這個詞:
\[ \boxed{\text{「人工智慧」(Artificial Intelligence) = McCarthy(1955)}} \]

1958 年他到 MIT,與 Marvin Minsky 共同創立 AI 實驗室。 - FORTRAN 的限制(1957): McCarthy 原本想用 FORTRAN 寫 AI 程式,但發現: - FORTRAN 不支援遞迴; - FORTRAN 只能處理數字(不能處理符號與串列)。

\[ \text{AI 需要符號計算(串列、樹、規則)} \ne \text{FORTRAN(數值計算)}. \]
  • IPL 的先例(1955–1956): Newell、Simon、Shaw 在 RAND 發明了 IPL(Information Processing Language)—— 第一個串列處理語言(1975 年兩人獲圖靈獎)——但 IPL 的語法很難用。
\[ \text{IPL(1955):功能有但難用} \xrightarrow{\text{McCarthy(1958)}} \text{LISP:功能 + 優雅}. \]
  • 7 個函數的極簡主義: McCarthy 的目標:用最少的原語(primitives)表達一切——
\[ \text{car, cdr, cons(串列操作)} + \text{eq, atom(判斷)} + \text{cond, lambda(控制)} = \text{一切}. \]

這是數學的極簡主義(對照 Peano 公理的極簡風格)。

線索與推理 -- 數學式、程式、理論#

第一條線索:S-運算式與「程式 = 資料」#

S-運算式(symbolic expression):LISP 的統一語法:

\[ \text{S-expr} = \text{atom} \mid (\text{S-expr}_1\ \text{S-expr}_2\ \cdots). \]

關鍵:程式與資料用同一種語法——

(+ 1 2)              ; 程式(求值後 = 3)
'(+ 1 2)             ; 資料(串列 (+ 1 2))

\[ \boxed{\text{程式可以操作程式(metaprogramming)}} \]
  • eval/apply 的循環:LISP 的核心:
\[ \text{eval(exp, env)} = \begin{cases} \text{atom} & \to \text{查 env}\\ \text{special form} & \to \text{特殊處理}\\ \text{函數呼叫} & \to \text{apply(f, eval(args))}\end{cases} \]
  • LISP 用 LISP 寫(McCarthy 1958 的奇蹟)——自舉(bootstrapping)。

第二條線索:遞迴與 lambda#

遞迴:函數呼叫自己——

\[ f(n) = \begin{cases} 1 & n = 0 \\ n \cdot f(n-1) & n > 0\end{cases} \]

lambda(匿名函數):

\[ \lambda x.\ x + 1 \quad\text{(Church 的 λ 演算,1936)}. \]

McCarthy 把 Church 的 λ 演算帶進了程式語言—— 函數式程式設計的起源。 - 高階函數:函數是「第一等公民」(可當參數、可當返回值)—— map、filter、reduce 的原型(LISP 的 mapcar)。

第三條線索:垃圾回收(GC)的發明#

問題:串列不斷被 cons 創造、被丟棄——記憶體怎麼辦? McCarthy 的發明(1959/1960):垃圾回收——

\[ \boxed{\text{定期掃描記憶體,回收「不可達」的物件}} \]

標記-清除法(mark-and-sweep): 1. 標記:從根部(stack、暫存器)可達的物件全部標記; 2. 清除:未標記的物件回收。

\[ \text{可達性} = \text{生命週期(GC 的核心思想)}. \]

現代 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–):
\[ \text{LISP} \xrightarrow{} \text{AI 的所有早期程式} \]
  • Logic Theorist(1956)、GPS(1957)(Newell–Simon,1975 圖靈獎);
  • ELIZA(1966)(Weizenbaum)——第一個聊天機器人;
  • SHRDLU(1970)(Winograd)——自然語言理解;
  • 專家系統(1980s)(Feigenbaum,1995 圖靈獎)——LISP 機器(Symbolics,1980s)的時代。
  • 程式語言的深遠影響(1958–):
\[ \boxed{\text{LISP 的發明散布到所有現代語言}} \]
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–):
\[ \text{LISP(1958)} \to \text{ML(1973,Milner,1991 圖靈獎)} \to \text{Haskell(1990)} \to \text{Scala(2004)}. \]
  • 類型系統:ML 的 Hindley–Milner 型別推論——現代語言的靈感;
  • React(2013):JS 的「UI = f(state)」——函數式思想;
  • Rust(2015):所有權系統 + 函數式。
  • 垃圾回收的世紀(1959–):
\[ \text{McCarthy(1959)} \xrightarrow{} \text{Java(1995)} \xrightarrow{} \text{Go(2009)} \xrightarrow{} \text{一切現代語言}. \]

沒有 GC,就沒有「安全的」高階語言—— C 的手動記憶體管理(malloc/free)是萬惡之源(緩衝區溢位、記憶體洩漏)。 - AI 的工具與思想(1958–): - McCarthy 1955 年發明「人工智慧」這個詞; - 1971 年圖靈獎的演講:《電腦作為符號計算的機器》; - 情境感知(situated AI,1990s)、常識知識庫(Cyc, 1984–)—— McCarthy 晚年的工作(常識推理)。 - 歷史定位:McCarthy 是「AI 的命名者」與「符號計算的工程師」——

\[ \text{Church 的 λ(1936)} \xrightarrow{\text{McCarthy 1958}} \text{LISP} \xrightarrow{} \text{AI + 函數式 + GC}. \]

現代程式語言的一半基因來自 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。