跳轉到

1958 - McCarthy 發明 LISP#

案件摘要#

1958 年,John McCarthy 在 MIT 以 λ 演算為內核,發明 LISP—— 史上第二古老的高階程式語言。他把「函數即資料」的推理帶入實作, 誕生了垃圾回收、遞迴函數、條件表達式與 eval/apply—— λ 演算從紙上理論首次成為可執行的程式語言。

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

  • 1956 年 Dartmouth 會議上 McCarthy 首次提出「人工智慧」一詞, 他的目標是讓電腦處理符號推理,需要一門「符號處理語言」。
  • 當時主流是 Fortran(1957),為數值計算設計,處理鏈結串列、符號 表達式極為笨拙。
  • McCarthy 曾閱讀 Church 的《The Calculi of Lambda-Conversion》(1941), 並受 Curry 的組合邏輯影響,深知「函數即一級公民」的威力。
  • 線索:Church 數字證明函數可用 λ 項表示——那麼資料也可以用 函數表示,程式與資料可以合而為一。

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

McCarthy 如何接觸 λ 演算#

  • McCarthy 在普林斯頓數學系讀博士(1951 年),期間研讀 Church 與 Curry 的著作。
  • 他最初嘗試以「函數表示法」設計語言,發現 λ 演算的記號可直接借用, 但他偏好更易讀的寫法:把 \(\lambda x.\ M\) 寫成 (lambda (x) M)。
  • 1958 年秋,McCarthy 的學生 Steve Russell 建議:eval 函數可以用 機器碼實作——LISP interpreter 就此誕生。

S-表達式:資料即程式#

LISP 的核心概念是 S-表達式(symbolic expression):

expr ::= atom | (expr expr ...)

λ 抽象寫成:

(lambda (x) (* x x))     ; 即 λx. x * x

呼叫:

((lambda (x) (* x x)) 5)  ; 求值為 25

資料與程式同構:

'(a b (c d))   ; quote 使 S-表達式成為資料
(car '(a b c)) ; 求值為 a

eval/apply:λ 演算的實作核心#

McCarthy 定義兩個相互遞迴的函數:

  • eval(e, a):求表達式 \(e\) 在環境 \(a\) 中的值
  • apply(f, x):將函數 \(f\) 作用於參數 \(x\)

eval/apply 的數學骨架就是 λ 演算的兩條規則:

\[ \text{eval}((\lambda x.\, M)\ N, a) = \text{eval}(M, a[x \mapsto \text{eval}(N, a)]) \]
\[ \text{eval}(x, a) = a(x) \]

CAR / CDR / CONS:鏈結串列的三劍客#

函數 意義 數學對應
(car x) 取串列首元素 \(x = (h, t) \Rightarrow h\)
(cdr x) 取串列尾 \(x = (h, t) \Rightarrow t\)
(cons h t) 建構配對 \(h : t\) (cons cell)

對應 λ 演算中的 Church 配對:

\[ \mathrm{pair} = \lambda h.\lambda t.\lambda f.\, f\ h\ t \]
\[ \mathrm{car} = \lambda p.\, p\ (\lambda h.\lambda t.\, h), \quad \mathrm{cdr} = \lambda p.\, p\ (\lambda h.\lambda t.\, t) \]

垃圾回收(Garbage Collection)#

LISP 首創 GC:cons cell 動態配置於 heap,當不再被引用時自動回收。 McCarthy 與團隊實作了「標記-清除(mark-and-sweep)」演算法:

1. Mark:從 roots(暫存器、堆疊)出發,標記所有可達 cell
2. Sweep:掃描整個 heap,回收未標記的 cell

這是現代 Java、Python、Go 等語言 GC 的共同祖先。

遞迴函數#

LISP 原生支援遞迴(當時罕見):

(defun factorial (n)
  (cond ((= n 0) 1)
        (t (* n (factorial (- n 1))))))

對應數學定義:

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

Y 組合子模擬匿名遞迴:

((lambda (f) ((lambda (x) (f (x x)))
              (lambda (x) (f (x x)))))
 (lambda (fact) (lambda (n)
   (if (= n 0) 1 (* n (fact (- n 1)))))))

一個簡單的 Python 版 LISP interpreter#

import math

def tokenize(s):
    return s.replace('(', ' ( ').replace(')', ' ) ').split()

def parse(tokens):
    t = tokens.pop(0)
    if t == '(':
        return [parse(tokens) for _ in range(int(tokens.count(')')))]
    try:
        return float(t) if '.' in t else int(t)
    except ValueError:
        return t

def parse_all(s):
    toks = tokenize(s)
    result = []
    while toks:
        result.append(parse(toks))
    return result

env = {'*': lambda a: math.prod(a),
       '+': lambda a: sum(a),
       '-': lambda a: a[0] - sum(a[1:]),
       'car': lambda a: a[0][0],
       'cdr': lambda a: a[0][1:],
       'cons': lambda a: [a[0]] + a[1],
       '=': lambda a: a[0] == a[1],
       'quote': lambda a: a[0]}

def eval(x, e=env):
    if isinstance(x, str):
        return e[x]
    if isinstance(x, (int, float)):
        return x
    op, *args = x
    if op == 'lambda':
        params, body = args[0], args[1]
        return lambda a: eval(body, {**e, **dict(zip(params, a))})
    if op == 'if':
        return eval(args[1] if eval(args[0], e) else args[2], e)
    if op == 'defun':
        e[args[1]] = eval(['lambda', args[2], args[3]], e)
        return args[1]
    f = eval(op, e)
    return f([eval(a, e) for a in args])

print(eval(parse_all('((lambda (x) (* x x)) 5)')[0]))        # 25.0
print(eval(parse_all("(car (cons 1 (cons 2 ())))")[0]))       # 1
print(eval(parse_all("(defun fact (n) (if (= n 0) 1 (* n (fact (- n 1))))) (fact 5)")[1]))  # 120

結案 -- 後果與影響#

  • LISP 成為 AI 研究的標準語言(直到 1990 年代),催生了 Scheme(1975)、 Common Lisp(1984)。
  • 垃圾回收成為現代語言的標配(Java、C#、Python、Go、JavaScript)。
  • 遞迴、一級函數、閉包進入主流:JavaScript、Python、Ruby 皆受影響。
  • eval/apply 模型成為所有 interpreter 的教科書架構。
  • LISP 的 S-表達式啟發了後世的 homoiconicity(同像性)與巨集系統。
  • McCarthy 後續提出 conditional expression(1960),補足了 Church 演算缺乏的條件式,成為現代 if-then-else 的起源。

關鍵人物與文獻#

  • John McCarthy(1927–2011):LISP 發明人,「人工智慧」一詞創造者。
  • Steve Russell:第一個 LISP interpreter 的實作者。
  • McCarthy, J. (1960). Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I. Comm. ACM 3(4), 184–195.
  • McCarthy, J. et al. (1962). LISP 1.5 Programmer's Manual. MIT Press.