图灵奖系列 · DoggyDad 原创
John McCarthy:Lisp 之父与“人工智能”命名者
John McCarthy:Lisp 之父与“人工智能”命名者
ANSWER-FIRST SUMMARY
本文回答什么问题
John McCarthy:Lisp 之父与“人工智能”命名者
- 主题分类:图灵奖系列
- 关键词:图灵奖、计算机历史、算法、人工智能、数据库、操作系统
- 人物实体:John McCarthy
图灵奖第六届 | John McCarthy:Lisp 之父与“人工智能”命名者
一句话概括:他用括号和递归定义了思考,用“时间共享”开启了云的序章,顺便给一个伟大的学科起了个名字——AI。
🏆 获奖简介
John McCarthy(约翰·麦卡锡)是人工智能的命名者、Lisp 语言的发明者、时间共享(Time-Sharing)构想的重要推动者。
- 出生时间:1927年9月4日
- 出生地点:美国 马萨诸塞州 波士顿
- 获奖年份:1971年
- 获奖原因:在人工智能领域的开创性贡献,特别是Lisp语言、时间共享、自动推理与形式方法。
🚀 他的重大贡献
1. 提出“人工智能”(Artificial Intelligence)
- 1956年达特茅斯会议的主要组织者与命名者。
- 奠定以“符号表示+推理”为核心的早期AI范式。
2. Lisp:AI 的“思维语言”
- 首创S-表达式、垃圾回收(GC)、一等函数、递归与宏等概念组合。
- 影响 Scheme、Clojure、Haskell、JavaScript(函数式与GC)等。
3. 时间共享与交互式计算
- 倡导多人同时使用大型机的“交互式”模型,是现代多用户与云计算思想的先声。
4. 形式化与常识推理
- 提出“情景演算”(Situation Calculus)、非单调推理等,推动机器常识推理研究。
🌍 影响力
- 没有 Lisp 与交互式计算,就没有后来AI编程生态与研究范式的繁荣。
- 从垃圾回收到REPL,从宏系统到元编程,他的思想深植现代语言与工具链。
🏅 获奖理由(通俗版)
他把“智能”从科幻带进工程,把“会思考的程序”变成可以书写、可以运行、可以推理的现实。
📝 文本风格与术语约定
- 引号:统一使用中文全角引号“……”。
- 术语:首次出现用“中文(英文)”并在必要处标注缩写,如“垃圾回收(Garbage Collection, GC)”“时间共享(Time-Sharing)”“情景演算(Situation Calculus)”“非单调推理(Non-monotonic Reasoning)”。
- 公式与代码:少量 KaTeX 表示数学(如 λ 演算),代码以 Lisp/Scheme/JavaScript 为主,便于读者拿来即用。
- 目标读者:工程师与对 AI 历史感兴趣的朋友;尽量“讲人话”,遇到生僻处配“通俗小抄”。
🌱 生平与时代:从达特茅斯到 SAIL
John McCarthy 出生在波士顿,少年时因身体原因在家自学很久,顺便把大学微积分教材啃得滚瓜烂熟——这段“被动宅”的时光,给了他对数学的深刻兴趣。后来他来到 MIT 与普林斯顿活动,和阿隆佐·邱奇(Alonzo Church)等人的“λ 演算(Lambda Calculus)”思想结缘。1956 年,他与 Minsky、Shannon、Rochester 等人组织了“达特茅斯会议(Dartmouth Workshop)”,首次正式提出“Artificial Intelligence”这个名字。
60 年代初,他在 MIT 推动“时间共享”与“交互式计算”的理念——“让多位用户像轮流坐公交一样共享一台大机器,人人都有‘虚拟的个人电脑’感受”。后来他去了斯坦福,创建著名的 Stanford AI Lab(SAIL),一度成为符号主义 AI 的旗舰阵地。McCarthy 的一生,大概可以用四个关键词概括:
- AI 的名字:把“人工智能”从一团朦胧的愿景,锚定为一个可讨论、可研究的学科。
- Lisp 语言:给机器提供“能像人类写数学公式一样写程序”的表达方式。
- 时间共享与交互式:把“一次批处理”变成“一边敲一边跑”的 REPL 体验。
- 常识推理与形式化:试图让机器具有“日常世界”的基本理解与推理能力。
小趣闻:晚年的 McCarthy 喜欢在自己主页上写“mccarthy@stanford.edu is my preferred medium of discussion”,并和网友在邮件与新闻组里认真讨论“AI 的下一步应该如何定义常识”。
🚀 关键贡献·深度展开
1) Lisp:机器的“符号思考”语
如果说 Fortran 像“工程手册”,Lisp 就像“数学家的草稿纸”。它把“程序”和“数据”统一成一种形态——S-表达式(S-expression):
(+ 1 (* 2 3)) ; 既是代码,也是数据
这叫“同像性(homoiconicity)”:代码就是一棵数据树。于是你可以写程序来改写程序,像数学家在黑板上涂涂改改一样自然。这直接催生了:
- 宏(macro):在编译/读入阶段对代码做变换,相当于“语言级的元编程”。
- 递归(recursion)与高阶函数(higher-order function):把“函数”当普通值传来传去。
- 垃圾回收(GC):既然都在生产、丢弃这些“树”,那就需要一种自动的“清扫阿姨”。
McCarthy 在 1960 年那篇著名论文《Recursive Functions of Symbolic Expressions》中,既阐述了 S-表达式与求值规则,也给出了垃圾回收的思路。若把现代语言圈比作“美食城”,Lisp 的摊位上挂着的牌子,几乎都写进了后来语言的菜单——Scheme、Clojure、Haskell、JavaScript 的闭包、宏或 GC 都能看到影子。
Lisp 一分钟体验:
;; 斐波那契(朴素递归)
(defun fib (n)
(if (< n 2) n (+ (fib (- n 1)) (fib (- n 2)))))
;; 一个简单宏:unless(条件不成立才执行)
(defmacro unless (cond &rest body)
`(if (not ,cond) (progn ,@body)))
(unless (> 3 5)
(print "3 并不大于 5"))
宏看起来像“替换模板”,实际是抽象语法树级别的变换,这种“伸缩语言”的能力,成为 AI 程序员在 60–80 年代快速探索的秘密武器。
GC 是怎么回事?
最朴素的思路是“标记-清扫(mark-and-sweep)”:从一组“根引用(roots)”出发,沿指针把可达对象“打勾”,扫掉没勾到的。后来又有“复制(copying)”“分代(generational)”等优化策略。通俗地说,GC 就像“会思考的清洁员”,知道哪些纸团还在用、哪些可以扔。McCarthy 的贡献在于把“自动回收”从工程小技巧,抬到了语言语义与运行时系统的“基本权利”。
类比:手动内存管理像是“租工作室,要记得每月交房租并按时退房”;GC 则是“托管公寓,系统会在你走后自动打扫,但你最好别到处乱放易碎品”。
2) 时间共享:从“批处理”走向“你现在就跑”
在早期,计算机像“剧院”:观众(程序)要排队,演出(运行)一次性完成。McCarthy 主张把大机器按时间片切分,轮流为多人服务——这就是时间共享(Time-Sharing)。它改变了计算的互动方式,带来了两件事:
- REPL(Read-Eval-Print Loop):读入一行,立即求值并打印结果。程序员可以“边想边试”。
- 教育与研究方式改变:学生在终端上立刻实验,而不是“交给机房,下周看结果”。
今天你在 Python/Scheme/Node REPL 里“打一行、见一行”,都能看到时间共享思想的回音。云计算与多租户,也可以视作它在 21 世纪的再版。
3) “常识推理”:让机器别总“答非所问”
McCarthy 的论文《Programs with Common Sense》(常被简称为 Advice Taker 框架)设想:把世界事实与规则写成逻辑陈述,让程序根据“新增陈述”自动得出“该做什么”。这让“程序=函数/过程”的传统观念被扩展为“程序=知识+推理”。他还提出:
- 情景演算(Situation Calculus):用“情景—动作—结果”的三元组形式化“改变中的世界”。
- 非单调推理(Non-monotonic Reasoning):新的信息到来时,旧结论可以被撤回;比如“鸟会飞”,但我们后来知道“企鹅是鸟”,于是对“企鹅会飞”的结论要撤销。
- 环绕化(Circumscription):一种偏好“最小异常”的形式系统,试图在“默认规则”与“例外”之间优雅地取舍。
这些看似“哲学味很重”的想法,实则非常工程:如果没有“默认+例外”的结构,知识库会像写不完的 if-else;有了非单调与环绕化,系统才不至于被一个新事实“卡死”。
4) AI 学派对话:符号主义、连接主义与后来者
McCarthy 倾向“符号主义(symbolic AI)”——把知识显式表示、让程序推理。但历史总是波浪形前进:
- 60–70 年代:定理证明、知识表示大放异彩;也经历了“微世界(micro-worlds)”与“可扩展性”的争论。
- 80 年代:专家系统热潮与第一次“AI 寒冬”的教训并存。
- 2010s 起:深度学习崛起,“连接主义(connectionism)”以端到端方式横扫感知任务。
今天的趋势更像“汇合”:大模型需要符号工具进行“外部记忆、规划、工具使用与可解释性”,而符号系统也借助统计/学习方法获得“从数据生长规则”的能力。McCarthy 若看到“LLM+工具+知识库”的组合,大概会说:“这才像话——要既会算,也要会讲道理。”
🔬 Lisp 深入浅出:从 λ 到 REPL 的快乐
A. 从 λ 演算到 S-表达式
λ 演算把“计算”极简化为“函数抽象与应用”,Lisp 把它“长成”工程可用的语言。S-表达式是带括号的树:
(f x y) ; f 应用于 x 与 y
((lambda (x) (+ x 1)) 41) ; 42
这让“宏”成为可能:
(defmacro -> (x &rest forms)
(reduce (lambda (acc f) (list (car f) acc (cadr f))) forms :initial-value x))
(-> 5 (list 1) (append '(2 3))) ; 类似 Clojure 的线程宏
B. 求值模型:环境与绑定
Lisp 的求值可用“环境(environment)”来解释:一个把名字映到值的字典。函数调用会创建新环境,递归就像“把自己再放一层”。闭包(closure)是“打包了外部环境的函数”,像把“工具和它的上下文”放进同一只箱子。
C. 垃圾回收:从标记清扫到分代
- 标记-清扫:标记可达对象,清掉其他。
- 复制回收:把存活对象搬到新空间,留下“白纸般干净”的旧空间。
- 分代回收:年轻对象多死得快,老年代少收;这与程序“多数对象朝生暮死”的经验吻合。
D. REPL 小实验:
如果你没有 Lisp 环境,也可以用 JavaScript 感受闭包与高阶函数——毕竟 JS 继承了大量 Lisp 的精神:
// Node.js 中试试看
function makeCounter() {
let x = 0;
return () => ++x; // 闭包:记住外部变量 x
}
const c = makeCounter();
console.log(c()); // 1
console.log(c()); // 2
REPL 哲学:别把“编译—链接—运行”的大循环当成唯一方式。交互地“问程序几个问题”,往往能更快把思路拧顺。
🧭 工程实践清单(给今天的开发者)
- 当你要“写一门 DSL”时,先想想:能否用“同像性”让规则=数据=代码?宏系统是否能把“样板”变成“语法”?
- 需要脚本化自动化时,优先考虑 REPL 与可组合的小函数,降低“修改—试错”的阻力。
- 知识工程/规则系统里,明确“默认与例外”,必要时引入非单调逻辑或“最小异常”策略,避免 if-else 地狱。
- 对需要“可解释、可审计”的系统,把“事实—规则—推理链”保存在日志中,让输出带“证据”。
- 面对“符号 vs 统计”的争论,不必二选一:工具箱里都放一把,谁合适用谁。
🧸 通俗小抄:一句话抓重点
- 同像性(homoiconicity):代码=数据,程序能像改数据一样改程序。
- 宏(macro):在“读/编译”阶段改写代码,相当于“语言级模板”。
- 垃圾回收(GC):自动清理不再使用的对象,防止内存泄漏。
- 时间共享(Time-Sharing):把一台大机按时间切片,营造“多人使用”的错觉,催生 REPL。
- 情景演算:用“情景→动作→新情景”表达“世界的变化”。
- 非单调推理:新事实能推翻旧结论,像更新认知一样更新逻辑。
- 环绕化(Circumscription):偏好“最小异常”的选择原则,优雅处理“默认+例外”。
🧪 迷你实验(拿来即跑)
这些例子不是为了“性能”,而是“可感知的思想”。
1) 一个极简 S-表达式解释器(Python 版,十几行)
# 仅演示:(+ 1 (* 2 3)) => 7
def parse(tokens):
token = tokens.pop(0)
if token == '(':
L = []
while tokens[0] != ')':
L.append(parse(tokens))
tokens.pop(0)
return L
elif token == ')':
raise ValueError('unexpected )')
else:
try:
return int(token)
except:
return token
def eval_ast(x, env):
if isinstance(x, int):
return x
if isinstance(x, str):
return env[x]
op, *args = x
if op == '+':
return sum(eval_ast(a, env) for a in args)
if op == '*':
r = 1
for a in args:
r *= eval_ast(a, env)
return r
raise NotImplementedError(op)
code = "(+ 1 (* 2 3))"
tokens = code.replace('(', ' ( ').replace(')', ' ) ').split()
ast = parse(tokens)
print(eval_ast(ast, {}))
这段小玩具展示了“读→解析→求值”的 REPL 精神。补上变量、函数与宏,离“真正的语言”就不远了。
2) 一个“unless”宏的 JavaScript 版(借助函数模拟)
function unless(cond, thunk){
if(!cond) thunk();
}
unless(3>5, ()=>console.log('3 并不大于 5'))
虽然 JS 没有宏,但高阶函数与闭包也能表达“控制结构”。
3) 默认与例外:非单调推理的玩具
# 默认:鸟会飞;例外:企鹅不会
facts = {('bird','tweety'), ('penguin','pingu')}
def can_fly(x):
if ('penguin', x) in facts: # 例外优先
return False
if ('bird', x) in facts: # 默认
return True
return None
print(can_fly('tweety')) # True
print(can_fly('pingu')) # False
这当然不是“真正的非单调逻辑”,但足以让你感受到“默认+例外”的工程味:可维护、可扩展、可落地。
🧷 术语对照表(精选)
- 同像性(homoiconicity)/ 宏(macro)/ 垃圾回收(GC)
- REPL / 时间共享(Time-Sharing)/ 交互式计算(interactive computing)
- 情景演算(Situation Calculus)/ 非单调推理(Non-monotonic Reasoning)/ 环绕化(Circumscription)
- 符号主义(symbolic AI)/ 连接主义(connectionism)/ 专家系统(expert system)
🙋 扩展 FAQ
Q:为什么 Lisp 看起来“全是括号”? A: 括号是树的外壳。它让“代码=数据”,宏能像“拿到树”那样改写程序。多数编辑器都有括号匹配与缩进,不必担心“走丢”。
Q:宏和函数有什么本质区别? A: 函数在“运行时”处理值;宏在“读/编译期”处理代码(语法树)。宏能定义新的控制结构,甚至像“扩建语言”。
Q:GC 会不会很慢? A: 早期是个担忧,如今分代/并行/增量 GC 十分成熟;关键在“分配模式”和“暂停时间”预算。大多数工程宁愿相信 GC 的正确性与可维护性。
Q:符号方法在深度学习时代还有意义吗? A: 有。规划、工具使用、外部记忆、可解释性等方面,符号系统提供“清晰的结构与证据链”。两者合璧常强于单独使用。
Q:Situation Calculus 和状态机有何不同? A: 状态机偏“编程模型”;情景演算偏“逻辑描述”,强调“行动导致的新情景”和“可证明的性质”,更利于知识表达与自动推理。
Q:为什么 McCarthy 的 Advice Taker 当年没火起来? A: 当时的硬件与知识工程工具还不够给力;此外,常识世界太复杂,必须处理默认、例外与不确定性。后来者在知识图谱、规则学习与可微逻辑上继续推进。
📅 生平时间线(简要)
- 1927:出生于美国波士顿。
- 1956:组织达特茅斯会议,“人工智能(AI)”命名问世。
- 1958–1960:提出 Lisp 与垃圾回收思想,发表 S-表达式求值经典论文。
- 1960s:推动时间共享与交互式计算;在 MIT/斯坦福活跃。
- 1960s–1970s:提出情景演算、常识推理研究框架。
- 1971:获图灵奖。
- 2000s:仍在公共邮件/网站上积极讨论 AI 常识问题。
- 2011:逝世。
注:具体年份以公开史料为准,本文聚焦思想脉络与工程影响。
🧾 轶事与评价
- McCarthy 喜欢把“函数名”起得像数学定义,Lisp 程序像在写证明;他常说“程序应像定理一样可读”。
- SAIL 的学生回忆:在实验室里写程序,像在哲学与工程之间搭桥,既能谈“默认逻辑”,又能立刻在 REPL 试运行。
- 他给了 AI 一个“可讨论的对象”:不是“像人一样思考”的空话,而是“用符号表达+逻辑推理”的路线图。
🔚 总结:把“我认为”变成“我能证明/我能运行”
McCarthy 的贡献,是把“智能”的讨论从“文学评论”拉回到“可执行的表达”:
- 用 Lisp 把“思想→程序”这条路修整到足够平坦,让程序能改写程序。
- 用时间共享与 REPL 把“实验周期”从天缩短到秒。
- 用情景演算、非单调与环绕化面对“常识世界”的复杂性。
今天,当我们把大模型接上工具链、知识库与规划器时,请记得:这条路并非凭空出现,它的地基正是当年“符号+逻辑+交互”的一砖一瓦。
📚 参考与延伸阅读(精选)
- McCarthy, J. “Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I.” 1960.
- McCarthy, J. “Programs with Common Sense.” 初版思路可追溯 1958,后续多次整理。
- McCarthy, J. “Situations, Actions, and Causal Laws.” 1963(思路延展至后续情景演算体系)。
- McCarthy, J. “Circumscription — A Form of Non-Monotonic Reasoning.” 1980.
- Steele, G. L. “Common Lisp the Language.”
- Abelson & Sussman. “Structure and Interpretation of Computer Programs.”
- Norvig, P. “Paradigms of Artificial Intelligence Programming.”
- Russell & Norvig. “Artificial Intelligence: A Modern Approach.”
📎 附录 A:Lisp 语言漫游(更深入但不烧脑)
1) 引用(quote)与求值(eval)
在 Lisp 里,'(1 2 3) 表示一个“原样数据”,而 (list 1 2 3) 表示“把 1、2、3 作为参数去构造一个表”。这就像拿着“黑板上的公式”和“算出来的数”的区分。eval 则是“把数据当代码再求一次值”,小心使用它,等同“给程序一把能修改自己的钥匙”。
(setq expr '(+ 1 (* 2 3))) ; 数据
(eval expr) ; => 7 (把它当代码再求一次值)
2) 尾递归与 Scheme 的“尾调用优化(TCO)”
尾调用就是“函数的最后一步就是调用另一个函数(或自己)”。如果实现做了 TCO,那这类调用不会额外增长栈空间:
(define (sum-from n acc)
(if (= n 0) acc (sum-from (- n 1) (+ acc n))))
这使得“用递归表达循环”成为可能。你会在 Haskell、Scala 等函数式语言里见到这种风格。
3) 续延(continuation)与 call/cc
续延可以理解为“保存当前的未来”。call/cc 把“下一步如何继续”当作一个函数传给你,于是你可以实现“早退、回溯、协程”等高级控制流。虽然听上去玄妙,但本质仍是“把控制流当值来传”。
4) 卫生宏(Hygienic Macro)
早期宏容易“变量名冲突”,Scheme 的 syntax-rules/syntax-case 引入“卫生”(自动避免捕获不该捕获的名字)。现代 Lisp 家族在“扩展性”与“稳健性”之间取得更好的平衡。
5) 持久化数据结构(Persistent Data Structures)
Clojure 借助结构共享(structural sharing)让“不可变”也很高效;这与垃圾回收是好朋友——对象生命周期清晰,推理容易。不可变世界里,调试像翻书找页码,状态不会悄悄改写你背后的小本子。
🧹 附录 B:垃圾回收的进化史(漫画式概览)
- 标记-清扫:先画圈再打扫,最容易理解。
- 复制回收:把活着的搬家,留下干净的旧房。
- 分代回收:新房区“勤打扫”,老房区“偶尔扫”。
- 三色标记:白-灰-黑,像过河排队,黑的安全,白的待清理,灰的在路上。
- 增量/并发:打扫与住人并行进行,减少“打扫期间请不要呼吸”的暂停。
- 精确 vs 保守:精确知道指针在哪里;保守模式则“凡是像指针的都当指针”。
现实工程里,Java/Go/JS/CLR 都在这条路线上各显神通。调优关键词:吞吐、暂停时间、内存占用、可预测性。
☁️ 附录 C:从时间共享到云计算的三段跳
时间共享 → 虚拟内存/多任务 → 现代虚拟化/容器/无服务器。把大机“切片”给多人用的精神没有变,只是“切片”的粒度越来越细:从分钟级作业到毫秒级函数调用。McCarthy 当年要的“人人能交互地用计算”今天变成“人人口袋里都是一台云端超算客户端”。
🧊 附录 D:两次“AI 寒冬”的教训(简述)
- 初次降温:研究资金与耐心有限,早期承诺超前于硬件与算法供给;英国的 Lighthill 报告等批评造成影响。
- 专家系统泡沫退潮:知识获取与维护成本高;系统脆弱,难以迁移到复杂域;硬件/学习方法尚未成熟。
经验之谈:
- 明确“可验证的成功标准”,别用“能 demo”代替“能规模化”。
- 记账“知识工程成本”,把规则的“默认+例外”设计好。
- 与统计学习/优化方法联合,形成“符号—数值”的闭环。
🧪 附录 E:写个袖珍规则引擎(Python 版)
class KB:
def __init__(self):
self.facts = set()
self.rules = [] # (cond, concl)
def tell(self, fact):
self.facts.add(fact)
def rule(self, cond, concl):
self.rules.append((cond, concl))
def infer(self):
changed = True
while changed:
changed = False
for cond, concl in self.rules:
if cond(self.facts) and concl not in self.facts:
self.facts.add(concl)
changed = True
kb = KB()
kb.tell(('bird','tweety'))
kb.rule(lambda F: ('bird','tweety') in F and ('penguin','tweety') not in F, ('can-fly','tweety'))
kb.tell(('penguin','pingu'))
kb.rule(lambda F: ('penguin','pingu') in F, ('cannot-fly','pingu'))
kb.infer()
print(kb.facts)
这只是“前向链式”玩具,真正的非单调需要“撤回结论”的机制;但它已体现“默认+例外”的可维护结构。
🧯 附录 F:常见误解 vs 事实
- “Lisp 只适合学术。”——不少公司把它当“胶水与原型利器”,宏系统让 DSL 开发极快;Clojure 在数据工程圈颇受欢迎。
- “GC 必定慢。”——慢的是不恰当的分配/对象生命周期;现代 GC 可在可预期的暂停内取得很好吞吐。
- “符号系统没用。”——一旦涉及约束、规则、规划与可解释要求,符号系统极具优势,且与学习方法互补。
- “REPL 只是玩具。”——它是缩短思维-反馈回路的利器,正如科学实验中的“快速试剂”。
- “括号太多不可读。”——编辑器会帮你;而且“统一的语法树”让复杂的宏与分析变得更简单。
🧰 附录 G:工程师的十条守则(受 McCarthy 启发)
- 程序应像定理一样可读:命名表达本意,注释解释原因。
- 先写小而可运行的东西,再抽象出通用部分。
- 用 REPL 快速验证假设,用测试固定住行为。
- 区分“默认与例外”,为将来的新事实留出弹性。
- 能用不可变就不可变,能局部化副作用就局部化。
- 把“配置/规则”当数据,避免把策略焊死在代码里。
- 日志不仅记录“发生了什么”,也记录“为什么得出这个结论”。
- 以组合子思维开发:小函数真香。
- 不迷信单一范式:让符号与统计、规则与学习彼此加分。
- 保有幽默感:解决问题的人类,也需要可维护的心情。
🧭 附录 H:边界条件与坑
- 宏过度:当宏让同事读不懂时,请考虑函数或代码生成器。
- 过度依赖
eval:安全与可维护性风险极高。 - 规则冲突:知识库需要冲突解决策略(优先级、时间戳、专业度等)。
- GC 与实时性:硬实时场景可能需要区域分配/手动管理或专门的实时 GC。
- 非单调逻辑的成本:推理可能指数级;为工程落地设置“近似与剪枝”。
📝 附录 I:练习(附参考答案要点)
- 用递归与尾递归各实现阶乘,对比栈增长与可读性。(要点:尾递归可 TCO;普通递归更直观。)
- 设计一个“unless”风格控制结构,用宏与高阶函数各实现一次。(要点:宏在编译期改语法,高阶函数在运行期改控制。)
- 写出一个“默认+例外”的规则集合,描述“猫通常不喜欢水,但虎猫是例外”。(要点:规则优先级/例外覆盖默认。)
- 画出一次 GC 的三色标记过程。(要点:白灰黑三段、不变式。)
- 用 Python 玩具解释器为
-与let增加支持。(要点:扩展读-解-求值循环。)
😂 附录 J:金句与段子
- “当你用 REPL 调试成功时,请记得保存那段代码,不然它会像宇宙射线一样随机消失。”
- “括号不是敌人,和它们交朋友,你就获得了‘宏的超能力’。”
- “默认永远不够用,例外总是姗姗来迟。”
- “当你把一个问题表示为事实与规则,它就不太会在凌晨三点叫你起床了。”
🧭 案例研究:一个日程助理如何“既懂规则又会学习”
目标:做一个能合理安排会议的智能代理,既能“守规则”,又能“学偏好”。
- 符号层(规则/约束):
- 工作日 9:00–18:00 可会议;午休 12:00–14:00 不开会。
- 同一参与者一天不超过 4 小时会议。
- 派对优先实体会议,代码评审可远程。
- 数据层(偏好/统计):
- 根据历史数据学到:A 喜欢上午、B 避开周五、C 更偏 30 分钟会议。
- 决策:
- 先用约束满足(CSP)找出所有可行时间,再用学习到的”偏好打分”排序。
- 解释:
- 输出”为何如此安排”的证据:“满足午休约束 + 尊重 A 上午偏好 + 减少跨时区代价”。
这就是“神经符号(neuro-symbolic)”的工程味:规则保证正确边界,学习负责细腻偏好。
🔗 现代语言中的 Lisp 影子(观察贴)
- JavaScript:一等函数、闭包、原型链;社区曾多次尝试“宏式变换”(如 sweet.js)。
- Python:REPL、生成器/装饰器具有“可组合”的 Lisp 气质。
- JVM 世界:Clojure 带来持久化数据结构、宏、transducers;Scala/Haskell 延续函数式血统。
- 编辑器即平台:Emacs Lisp 把“编辑器”变成“可编程操作系统”。
🧠 Scheme / Common Lisp / Clojure 简要对比
- Scheme:极简核心,强调尾调用、卫生宏与教学价值。
- Common Lisp:工业化标准,拥有丰富库与面向对象(CLOS)。
- Clojure:运行在 JVM/JS 平台,强调不可变、并发友好与数据驱动哲学。
把它们看作“一棵树的三根枝条”更合适:核心都是“同像性 + 函数式 + 宏/组合”。
λ🎓 小课堂:Y 组合子与匿名递归(JS 版)
// Y Combinator(简化演示)
const Y = f => (x => f(v => x(x)(v)))(x => f(v => x(x)(v)));
const fact = Y(self => n => n<=1 ? 1 : n * self(n-1));
console.log(fact(5)); // 120
Y 组合子让“没有名字的函数”也能递归自己。它常被当作“函数式魔术”,但本质是“把递归作为参数传进去”。
🔁 玩具回溯:用 Python 感受逻辑编程的味道
def backtrack(vars, domain, ok, sol=None):
sol = sol or {}
if len(sol)==len(vars):
return [sol.copy()]
v = vars[len(sol)]
out=[]
for val in domain[v]:
sol[v]=val
if ok(sol):
out+=backtrack(vars, domain, ok, sol)
del sol[v]
return out
# 例:给 A,B 取 1..3,且 A!=B
sol = backtrack(['A','B'], {'A':[1,2,3],'B':[1,2,3]}, lambda s: s.get('A',0)!=s.get('B',0))
print(sol)
这与 Prolog 的“生成+检验”精神相通:声明约束,让搜索替你“试”。
🧩 新趋势:可微逻辑、程序归纳与工具增强 LLM
- 可微逻辑:把逻辑算子变成“可导”的形式,让学习能够“摸到逻辑”。
- 程序归纳(Program Induction):让模型学会“写算法/DSL 片段”,把知识固化在可执行结构里。
- 工具增强 LLM:把符号世界(数据库/规则/代码执行器)接到大模型的“嘴”上,让它既能“说”,也能“做”。
McCarthy 的“知识可显式化、推理可验证”的愿景,在这一波技术融合中正得到新的舞台。
📚 延伸阅读再加一页
- Greenblatt vs Symbolics 的 Lisp Machine 故事(可搜索“AI Lab 分家”):工程、商业与社区文化的一次剧烈碰撞。
- Steele & Sussman 的“巫师书”(SICP):用 Scheme 讲述“计算的本质”,很多读者称其“打开了编程的第三只眼”。
- Shriram Krishnamurthi 的《Programming Languages: Application and Interpretation》:现代 PL 的可运行教材。
- Norvig 的 PAIP:用 Common Lisp 演示搜索、规划与知识表示的范式。
🧾 术语索引(加长版)
- λ 演算(Lambda Calculus)/ 组合子(Combinator)/ Y 组合子
- 同像性 / 宏 / 卫生宏 / 语法对象(syntax object)
- 闭包 / 高阶函数 / 尾调用优化(TCO)
- 垃圾回收:标记-清扫 / 复制 / 分代 / 增量 / 并发 / 三色标记
- REPL / 时间共享 / 交互式计算 / 虚拟化 / 容器
- 情景演算 / 非单调推理 / 环绕化 / 框架问题(frame problem)
- 专家系统 / 知识工程 / 约束满足(CSP)/ 规划(Planning)
- 神经符号 / 可微逻辑 / 程序归纳 / 工具增强 LLM
🧠 FAQ·第二辑(更刁钻的问题)
Q:宏是不是会让调试变得困难? A: 可能。务必使用“展开(macroexpand)”工具并记录展开前后的代码;合理抽象与命名能显著缓解。
Q:为什么很多 Lisp 项目都“很聪明,但不流行”? A: 生态与时机很重要。Lisp 的思想常经由其他语言渗透进入主流(闭包、GC、REPL、宏风味的元编程)。
Q:逻辑系统如何与不确定性、概率结合? A: 通过“概率逻辑、马尔可夫逻辑网络、可微逻辑”等;也可以“先规则滤掉不可能,再用概率模型打分”。
Q:Clojure 的不可变会不会让性能变差? A: 结构共享让大部分更新是 O(log n) 或摊销常数;配合 JVM 优化与并发场景,常能换来更低的协调成本。
Q:Emacs 为什么像“小操作系统”? A: 因为它把“编辑器状态”都暴露给了 Lisp 层,任何功能都能脚本化;这正是 McCarthy“交互式计算”梦想的自然延伸。
🗣️ 语录与观点(意译)
- “计算机的重点不在于计算,而在于表达。”(把想法表达为可执行结构)
- “常识并不普通;它是最不普通的资源。”(需建模、需维护)
- “时间共享不是奢侈品,而是想清楚问题的必要条件。”(交互式实验加速思考)
- “不要被实现细节绑架;语言应为人服务。”(抽象与宏系统的意义)
🧑🏫 自学 Lisp 的 4 周路线(可调整)
第 1 周:
- 选择一门方言(Scheme/CL/Clojure 皆可)。
- 体验 REPL:写 20 个一行函数;实现 map/reduce/filter;读 S-表达式与 quote。
第 2 周:
- 写 3 个宏:
unless、线程宏、简易match。 - 实现一个迷你解释器或扩展现有解释器的小功能。
第 3 周:
- 做一个 DSL:比如“单位换算器/构建系统的小语法/数据校验器”。
- 研究 GC 行为:写一个会产生大量短命对象的小基准。
第 4 周:
- 知识与推理:实现一个小规则库(默认+例外)。
- 结合现代系统:把 Clojure 接到 JVM 生态的数据/消息队列;或用 Guile/Emacs Lisp 改造编辑器工作流。
🧪 课堂小游戏(附提示)
- 给列表加“管道操作符”:
(-> data (filter p) (map f))(提示:宏展开成嵌套调用)。 - 写个
defrecord风格的构造器宏(提示:生成make-foo与字段访问器)。 - 把“单位换算”表达为数据,然后让宏生成函数(提示:同像性=数据即代码)。
- “可撤回的结论”:给规则引擎加一个“撤回栈”(提示:记录推理链)。
- 用
call/cc实现“早退”(提示:把“未来”当作函数参数)。
🧱 经典系统的血缘关系(鸟瞰)
- STRIPS 规划语言与情景演算/状态描述有共鸣,影响了后来的 PDDL 系列。
- 逻辑编程(如 Prolog)与“知识=规则”的思想相通,在搜索与约束方面与 Lisp 系统互补。
- Lisp Machines 的尝试展示了“语言特定硬件/系统”的极端形态,虽未主流化,但在 IDE/调试体验上影响深远。
🤝 McCarthy 与同时代人的“友好对话”
- 与 Marvin Minsky:一起推动早期 AI,但在“微世界/规模化”等问题上有不同侧重。
- 与 John Backus:后者以 Fortran 与 FP 提出“编程的新愿景”,与 Lisp 的函数式传统互相呼应。
- 与 Alan Turing:在“可计算性/错误模型/机器智能”上前辈开路,McCarthy 为“表达与常识”续写篇章。
🧰 给今天的代理开发者:如何“把 McCarthy 接上 LLM”
- 事实层:把日历/数据库/知识图谱暴露为结构化工具;
- 规则层:把“默认+例外”“业务约束”写成可检查的规则;
- 计划层:用符号规划器(或约束求解器)生成备选方案;
- 语言层:让 LLM 负责“理解意图、生成解释、兜底常识”;
- 监督层:记录推理链与调用轨迹,支持回放与审计。
一句话:用符号系统“划边界和出证据”,用 LLM “填细节与讲人话”。
🧭 开发工具清单(建议)
- Clojure + REPL + nREPL/CIDER:快速迭代与生产级部署并存。
- Racket/Scheme:做语言/DSL 原型的乐园。
- Emacs Lisp:改造你的工作流,把重复劳动变成命令。
- MiniKanren/Prolog:当规则/搜索登场时的锋利小刀。
- Z3/SMT:当你需要“机器帮你证明”时的重型设备。
📌 里程碑与术语年表(简化版)
- 1956:达特茅斯会议,“AI”一词确立。
- 1958–1960:Lisp、S-表达式、GC 思想成熟。
- 1960s:时间共享、交互式计算;情景演算提出。
- 1970s:非单调推理的雏形与讨论热烈;专家系统兴起。
- 1980:环绕化论文提出“最小异常”的代表性方案。
- 1990s:Lisp 在工业界的地位被更主流的生态取代,但其思想渗入各语言。
- 2000s–:Clojure 崛起;PL/AI 融合推进;LLM 时代再度激活符号工具链。
🧭 给读者的“实操任务单”
- 选一个你日常工作里的“规则密集”小问题(审批、排班、监控告警),尝试用“默认+例外”重写一版。
- 把一段重复脚本迁移到 REPL+宏的工作流,记录你节省的时间。
- 用 Clojure 的不可变数据结构改造一个并发模块,看看 bug 曲线是否下降。
- 把一个 LLM 方案接入“规则/规划层”,比较“仅 LLM”与“LLM+规则”的稳定性与可解释性差异。
🧩 彩蛋:把“数学气质”带回编程
McCarthy 的程序往往像数学命题:清晰陈述前提与结论,中间用可复查的步骤连接。若你也想这样写代码,不妨在提交信息里加一段“定理风格”的描述:“在假设 A、B 条件下,我们用规则 R 推出 C,并用案例 D 验证”。代码就会逐渐长成“可证明的工程”。
🧩 附录 K:Frame Problem 与“扩展容忍(Elaboration Tolerance)”
当你描述“世界会变化”时,最大的麻烦是:你只想说“我把杯子从桌上拿起”,却不得不列出一长串“不变的事实”——比如“天花板仍然在上面”“墙壁没动”“地球没有倒转”。这就是框架问题(Frame Problem)。
McCarthy 提出“扩展容忍”:好的知识表示方式,应该在加入新规则/行动时,不需要重写半个世界。环绕化与默认推理,提供了工程化的解法:
- 默认“除了被行动明确改变的性质,其他都不变”。
- 只有当出现证据(新的规则/事实)时,才撤回默认。
这就像 Git 的增量提交:你只记录“变了什么”,其他沿用旧版。不必每次都重写整本百科全书。
🔧 附录 L:方块世界(Blocks World)的小型规划示例
用 STRIPS 风格描述“把 A 堆到 B 上”的操作:
Action: Stack(x, y)
Pre: Clear(x) ∧ Clear(y) ∧ OnTable(x)
Add: On(x, y)
Del: OnTable(x) ∧ Clear(y)
解释:
- 先决条件(Pre)告诉我们何时能做这步;
- Add/Del 列出执行后的变化(增删事实);
- 其他事实默认保持不变(这正是“框架问题”的工程解法)。
一个玩具搜索器的思路:
- 把目标分解为子目标(如需要
On(A,B),就考虑Stack(A,B))。 - 检查先决条件是否成立;不成立则递归地把先决条件变成目标。
- 使用“回溯+剪枝”寻找行动序列;
- 每次应用动作时,更新事实库(Add/Del)并继续搜索。
虽然这类规划器在真实世界会遇到组合爆炸,但它们在“受限场景”非常实用,也启发了后来的 PDDL 族与混合规划系统。今天,你可以把“目标分解/动作库”交给符号规划器,再用 LLM “解释计划、补全细节、进行异常处理”。
🔮 附录 M:写一个“迷你 match 宏”(Common Lisp 思路)
有时我们想写:
(match expr
((list 'add a b) (+ a b))
((list 'mul a b) (* a b))
(_ (error "Unknown")))
用宏把它展开成嵌套的 cond/if:
(defmacro match (expr &rest clauses)
(let ((e (gensym "e")))
`(let ((,e ,expr))
(cond
,@(mapcar (lambda (c)
(let ((pat (car c))
(body (cadr c)))
(cond
;; 形如 (list 'add a b) 的简单三元列表匹配
((and (consp pat) (eq (car pat) 'list))
(let ((head (cadr pat))
(a (caddr pat))
(b (cadddr pat)))
`((and (consp ,e)
(eq (car ,e) ',head)
(= (length ,e) 3))
(let ((,a (cadr ,e)) (,b (caddr ,e))) ,body))))
;; 通配分支 `_`
((eq pat '_) `(t ,body))
;; 精确相等匹配
(t `((equal ,e ',pat) ,body)))))
clauses))))
这只是“演示性质”的宏,真实世界请使用成熟库(如 optima 等)。但它展示了“语法即数据”的威力:把你想要的句法“直接写成数据”,宏替你铺路。
🧠 FAQ·第三辑(实践中的抉择)
Q:规则系统如何与数据库协同? A: 把数据库当“事实来源”,规则当“派生器”;对变化大的事实采用订阅/事件流,保持规则引擎与数据源一致性。
Q:如何给推理链做可视化? A: 把“使用的事实/规则/时间戳/作者”作为有向图输出,附带自然语言解释,既便于审计也便于回放。
Q:宏与类型系统怎么取舍? A: 宏管“语法变换”,类型管“静态约束”;两者并不冲突。你可以在宏展开后经过类型检查。
Q:什么时候该用 Prolog/约束求解器而不是把规则硬写在代码里? A: 当规则数量多、频繁变化、需要解释/回退时;当搜索/约束本身是问题核心时。
🪄 后记:这篇文章是如何“REPL 化”写出来的
写作过程中,我先在交互环境里搭了几个“最小可运行”的实验:S 表达式解释器、默认+例外规则、回溯搜索。每写一段解释,就在 REPL 验证一遍,让文字和代码互相校对。McCarthy 强调“表达”和“交互”,这种工作流让“想法—实验—修改—再表达”形成闭环。若你也在写技术文章或设计新工具,不妨把 REPL 当作写作的共创伙伴。
DISCUSSION
评论与补充