Paul Graham:the Roots Of Lisp@2002

以下是根據提供的文章《The Roots of Lisp》所提取的主要論點及詳細解釋:

這篇文章的核心旨在深入闡述約翰·麥卡錫(John McCarthy)在 1960 年發表的一篇劃時代論文中所提出的 Lisp 語言的根本思想。作者保羅·格雷厄姆(Paul Graham)將麥卡錫的工作比作歐幾里得之於幾何學——展示了如何從一小組簡單的操作符和函數記法構建出一個完整的程式語言。文章不僅將 Lisp 視為計算機歷史上的里程碑,更將其視為現代程式設計發展趨勢的模型。

主要論點可以歸納為以下幾點:

  1. Lisp 的基礎:基於簡單原始操作符和函數記法的精簡計算模型。

    • 文章開頭即指出,麥卡錫證明了如何僅使用「一小撮」簡單的操作符和一套函數記法來構建整個程式語言。這組基礎操作符包括:quoteatomeqcarcdrcons 以及 cond。這些操作符被定義為語言的「公理」,所有更複雜的功能都將基於它們來定義。
    • quote:這個操作符是 Lisp 獨特之處的關鍵,它阻止了其參數的求值,直接返回參數本身。這使得 Lisp 能夠區分「代碼」和「數據」,因為代碼本身也是 Lisp 的列表結構。 quoted 的列表被視為靜態數據,而未 quoted 的列表(作為表達式的第一個元素)則被視為待執行的代碼或函數調用。這種代碼與數據的統一性是 Lisp 強大的基礎之一。
    • atom:用於檢查其參數的值是否為原子(非列表)。
    • eq:用於檢查兩個原子或空列表的值是否相等。
    • car:返回列表的第一個元素(head)。
    • cdr:返回列表除第一個元素外的其餘部分(tail)。
    • cons:將一個元素添加到一個列表的開頭,構造一個新的列表。
    • cond:提供條件判斷的能力,類似於其他語言中的 if-else 結構,但可以包含多個條件分支,按順序求值,直到找到第一個為真的條件,然後返回對應的表達式的值。
    • 這些原始操作符中的大多數(如 atom, eq, car, cdr, cons)在求值時會先求值其參數,這類操作符稱為「函數」。而 quotecond 的求值規則則有所不同,它們不會無條件地求值所有參數。這種精簡的基礎集合證明了複雜計算可以從極其簡單的概念中湧現。
  2. 程式碼即數據:Lisp 使用統一的列表結構(S-表達式)來表示程式碼和數據。

    • 這是 Lisp 最具定義性的特徵之一。文章解釋了 Lisp 的表達式(Expression)定義:要麼是原子(如符號),要麼是零個或多個表達式組成的列表。這種列表結構被稱為 S-表達式(S-expression)。
    • 範例 (a b (c) d) 清楚地展示了列表的巢狀結構。
    • 關鍵在於,不僅數據可以使用這種列表結構表示,Lisp 的程式碼本身也是用這種結構寫成的。例如,函數調用 (operator arg1 arg2 ...) 是一個列表,其中第一個元素是操作符或函數名,後面是參數。函數定義 (lambda (p1 p2) body) 也是一個列表。
    • 這種統一性使得 Lisp 程式可以方便地處理(產生、分析、轉換)其他 Lisp 程式碼,這是宏(Macros)等強大元程式設計能力的基礎,雖然文章沒有詳細討論宏,但代碼即數據的原則是其前提。quote 操作符正是實現這種區分和處理能力的關鍵工具。
  3. 自我解釋性:Lisp 的核心在於其能夠在自身內部定義一個解釋器 (eval)。

    • 這是文章中最令人驚訝和深刻的部分。作者展示了如何使用 Lisp 的原始操作符和先前定義的輔助函數(如 assoc.pair.append. 等)來編寫一個名為 eval. 的函數。
    • eval. 函數接收兩個主要參數:要被求值的表達式 e 和當前的「環境」a。環境是一個列表,記錄了原子(變量名)與其對應值之間的綁定關係,通常是通過函數調用中的參數綁定建立的,使用 pair.assoc. 來構建和查詢。
    • eval. 函數通過一個 cond 結構來處理不同類型的表達式:
      • 如果 e 是原子,它在環境 a 中查找該原子的值 (assoc. e a)。
      • 如果 e 是一個列表且第一個元素是原始操作符(如 quoteatomeq 等),eval. 會根據該操作符的求值規則來處理,對於需要參數求值的情況,它會遞歸調用 eval. 來求值參數。cond 表達式則通過輔助函數 evcon. 處理。
      • 如果 e 是一個列表且第一個元素是一個函數(通過 lambdalabel 定義),eval. 會求值函數的參數(通過輔助函數 evlis.),使用這些值和函數定義中的參數列表來創建一個新的環境,然後在新的環境中求值函數的主體表達式。label 還會將函數名與函數本身添加到環境中,以便遞歸調用。
      • 如果 e 的第一個元素是一個原子,而該原子在環境中綁定到一個函數,eval. 會取出綁定的函數定義,構造一個新的表達式並遞歸求值。
    • eval. 的存在證明了 Lisp 語義的緊湊和優雅。它表明了語言的核心行為可以用語言本身來描述和實現。這不僅是一個理論上的突破,也為 Lisp 實現的簡潔性奠定了基礎。理解 eval 就是理解 Lisp 的本質。
  4. Lisp 作為計算模型的意義深遠,並影響了後來的程式語言發展。

    • 文章將麥卡錫的 Lisp 與圖靈機(Turing Machine)等其他計算模型進行比較,指出 Lisp 提供了一個更抽象、更易於描述算法的模型。
    • 儘管麥卡錫 1960 年的原始 Lisp 存在一些限制(如缺乏副作用、沒有實用的數字類型、使用動態作用域等),但文章強調這些限制可以在核心 eval 模型上通過少量額外代碼來解決。
    • 最重要的是,作者認為 Lisp 模型代表了程式設計的「高地」,而 C 模型是另一處高地。在過去幾十年裡,新的程式語言一直在穩步向 Lisp 模型靠攏,例如許多語言增加了運行時類型(runtime typing)和垃圾回收(garbage collection)等特性,這些都是 Lisp 早期就擁有的。
    • 因此,理解 eval 不僅是學習一段計算機歷史,更是理解未來程式語言可能走向何方,以及計算本身的深層原理。Lisp 並非僅僅是為人工智能或快速原型開發而設計的語言,它更像是麥卡錫在嘗試「公理化計算」時所「發現」的一種自然形態。原始論文中的一些「錯誤」(如動態作用域導致的問題、原始 eval 中的小 bug)也體現了在實際實現和應用中對這些概念的探索過程,並最終促成了語言的改進(如 Scheme 引入詞法作用域)。

總結來說,這篇文章透過回溯 Lisp 的源頭,詳細解釋了 Lisp 語言如何從極少量的基本概念(原始操作符、列表結構、函數記法)構建起來,特別是通過展示其核心解釋器 eval 可以用語言自身表達,揭示了 Lisp 作為一種自我解釋、高度抽象且優雅的計算模型。這種模型不僅是歷史的見證,更是影響並引導著現代程式語言發展的重要力量。理解 Lisp 的根源,特別是 eval 的工作原理,能夠深刻理解程式設計的本質和未來的發展方向。