
视频元信息
- 视频标题:6.001 计算机程序的构造与解释(SICP)1986传奇原版·全20讲·中英双语字幕 p07 6-符号微分与引用
- 课程来源:MIT 6.001(1986 年原版,讲师:Hal Abelson / Gerald Jay Sussman)
- 本讲主题:符号微分(Symbolic Differentiation)与引用(Quotation)
- 时长:约 30 分钟
内容摘要(TL;DR)
本讲承接上一讲的数值微分,转向符号微分:直接对代数表达式(而非函数)求导,用递归程序封装微积分教科书中的求导法则。核心要点包括:求导是归约式(reduction)规则、表达式与语言同构(用 Lisp 列表表示代数式)、引用(quote) 机制的必要性,以及通过抽象屏障更换表示层(如内建化简)来改善输出质量。
一、开场:稳健系统的核心思想 [00:19]
讲师首先回顾上一讲的核心思想:如何构建稳健(robust)系统。
- 关键思想:一个稳健的系统必须对小改动敏感——问题的小改变应当只导致解的小改变。
- 连续性(continuity):解空间应当在问题空间中连续。
- 理解:这是工程系统设计中"局部性"原则的体现——模块化、抽象屏障、递归分解都是为了让系统对局部改动保持局部响应,而不是牵一发动全身。
- 易错点:转录中 [00:48] 至 [02:39] 出现大段中文乱码(疑似语音识别错误或字幕错位),内容无法还原,不影响后续主线。此段大致是讲师在解释"如何做到这一点"的过渡段落。
二、从数值微分转向符号微分 [02:39]
讲师明确本讲与上一讲的区别。
- 上一讲(见本视频前段及上一讲)讨论的是导数过程(derivative procedure):一个操作过程的过程,属于数值方法,得到的是导数的数值近似。
- 本讲转向另一种东西:微积分教科书中的求导法则——用于对代数语言写出的表达式求导。
- 典型法则:
- 常数的导数为 0;
- 对某变量求导时,该变量自身的导数为 1;
- 常数乘以函数的导数 = 常数乘以该函数的导数;
- 以此类推。
- 重点:这些是精确表达式(exact expressions),不是数值近似。
- 思考:数值微分把函数当作"黑箱"(只能喂数、取数),符号微分则要求"打开"表达式、看它的语法结构——这是语法层面(syntactic) 的操作。
三、求导为何容易、积分为何困难 [04:00]
讲师用求导法则与积分法则的对比,引出"归约规则"的概念。
3.1 求导法则的方向性
- 每条求导法则按箭头方向使用:
- 左侧:与你的表达式做模式匹配;
- 右侧:该表达式的导数。
3.2 关键性质:右侧是左侧的真子表达式
- 每条法则中,右侧仍处于"导数"内部的表达式,都是左侧表达式的真子表达式(proper sub-expression)。
- 例:和的导数 = 各部分的导数之和——问题被拆成更小的同类问题。
3.3 归约规则与递归
- 红色重点:向右的规则是归约规则(reduction rules)——问题变得更简单。
- 把一个大而复杂的问题,转化为一堆更小的问题,再合并结果——这正是递归(recursion) 的完美用武之地。
3.4 反向(积分)为何难
- 积分方向存在多重困难:
- 多条规则同时匹配:例如对"和"积分时,可能有多条规则都适用,不知道该选哪条,且不同选择可能通向不同结果。
- 表达式会变大:沿积分方向,表达式倾向于膨胀。
- 无法保证终止:表达式变大后,所选路径不一定终止,除非发生"偶然的抵消(accidental cancellation)"。
- 结论:积分是复杂的搜索问题,非常难做。本讲只做求导。
易错点:不要混淆"导数的过程"(作用于函数、数值近似)与"导数的表达式"(作用于代数式、语法操作)。前者是数值方法,后者是符号方法。
四、把求导法则写成程序 [06:22]
讲师开始现场书写求导程序,核心是分情况分析(case analysis)。
4.1 程序接口
- 过程名:
deriv,参数为表达式(exp) 和变量(var)。 - 重点:
deriv是"对表达式求导",不同于"对函数求导"。 - 关键区别:函数无法被"打开",只能得到答案;而表达式的导数取决于它如何被书写,因此是语法现象(syntactic phenomenon)。
- 理解:这就是为什么本讲大量时间都在"操心语法"——表达式的结构决定了求导过程如何展开。
4.2 分情况分析
- 递归处理复杂问题,必须以分情况分析开头,通常是一个大型条件表达式(cond)。
- 可能的情况:
- 表达式是常数(相对给定变量):结果为 0。
- 理解:导数表示变化率,常数不变化,变化率为 0。
- 表达式就是所变动的变量本身:结果为 1。
- 理解:变量对自身的相对变化率为 1。
- 表达式是和(sum):按和的求导法则,结果为各部分导数之和。
- 两个加数分别记为
a1、a2(讲师说黑板上写不下"addend""augend"这样的长名字)。 - 结果:
(make-sum (deriv a1 var) (deriv a2 var))。
- 两个加数分别记为
- 表达式是积(product):按乘积法则。
- 乘积的导数 = 第一个乘以第二个的导数 + 第二个乘以第一个的导数。
- 结果:
(make-sum (make-product m1 (deriv m2 var)) (make-product (deriv m1 var) m2))。
- 表达式是常数(相对给定变量):结果为 0。
- 可以继续添加更多法则(商、幂等),直到覆盖完整的微积分法则集。
4.3 谓词命名约定
- 红色重点:定义谓词过程(predicate procedure)(返回真/假)时,名字应以问号
?结尾。 - 问号本身没有语义,只是人类之间的约定接口(conventional interface between humans),便于阅读程序。
- 思考:这是 Lisp 社区沿用至今的风格约定(如
null?、even?),属于"给人看的"而非"给机器看的"信息。
4.4 类型分派(dispatch on type)
- 关键概念:程序中对表达式类型做分派(dispatch)——判断它是常数、变量、和还是积。
- 红色重点:类型分派在构建语言时绝对必要,因为语言正是由不同类型的表达式构成的。
- 拓展:这一思想后来发展为"数据导向编程(data-directed programming)"和"消息传递(message passing)",是 SICP 后续章节的核心。
五、表示层:用列表结构表示代数表达式 [11:30]
讲师说"现在是时候玩表示(representation)了",并引入一个关键的"双关"。
5.1 核心双关(pun)
- 关键思想:如果要表示和、积、差、商等代数表达式,为什么不直接用写程序所用的同一种语言?
- 即:程序中的代数表达式形如"a 乘以 x 的积与 b 乘以 x 的积之和",那就直接用这种形式作为表示。
- 好处:任何这样的对象都有明确的car(运算符)和cdr(操作数),无需自己解析——解析工作已经由 Lisp 的列表结构"免费"完成了。
- 理解:这就是同像性(homoiconicity) 的雏形——语言的表达式用语言自身的数据结构表示。
5.2 表示的具体形式
- 和:
(+ a1 a2),即包含符号+、a1、a2的列表。car是符号+;(car (cdr ...))(即 cadr)取到a1;(car (cdr (cdr ...)))(即 caddr)取到a2。
- 积:
(* m1 m2),同理。
5.3 定义基础谓词
- 常数判断
(constant? exp var):- 表达式是原子(atomic) 的(不能被拆成 car 和 cdr,不是列表);
- 且不恒等于(eq?) 该变量。
- 理解:这里只处理原始常数;更复杂的常数表达式不在本讲范围内。
- 同变量判断
(same-variable? exp var):- 表达式是原子,且恒等于该变量。
- 变量用符号(symbol) 表示,如
x、y。
易错点:
constant?中的"常数"是相对于给定变量而言的。同一个表达式对x是常数,对a就可能不是。这是求导过程唯一的"自由度"所在(见第七节)。
六、引用(Quotation):本讲最深刻的概念 [14:20]
讲师在定义
sum?时引入了quote,并由此展开一段关于语言哲学的讨论。
6.1 为什么需要引用
- 定义"表达式是否为和"时,需要判断
car是否为符号+。 - 如果直接写
+,程序会去求值+(即加法过程),而不是判断"这里是不是那个符号+"。 - 因此需要
(quote +)或简写'+,表示"我要谈的是这个符号对象+本身"。
6.2 引用的语言哲学:名称与提及(name and mention)
- 讲师用英语歧义举例:"Say your name" 与 "Say 'your name'" 在口语中无法区分,书写中用引号区分。
- 红色重点:
quote就是 Lisp 中区分"谈论表达式本身"与"表达式在谈论别的东西"的记号。 - 理解:这是使用(use)与提及(mention) 的经典区分,可追溯到语言哲学。
6.3 引用的危险性:指称晦暗语境
- 讲师给出一个"演绎":
- 前提 1:Chicago 有七个字母;
- 前提 2:Chicago 是伊利诺伊州最大的城市;
- 若可随意"以相等代相等",就会推出"伊利诺伊州最大的城市有七个字母"——明显错误。
- 红色重点:
quote是指称晦暗语境(referentially opaque context) 的原型。在引用内部,不能随意替换相等项。 - 结论:一旦语言中加入
quote,语言就变得复杂得多——"以相等代相等"不再无条件成立。 - 拓展:这是编程语言理论中"引用透明性(referential transparency)"的反面案例。引用透明性是函数式编程的重要性质,而
quote恰恰打破了它。
易错点:
quote不是可有可无的语法糖。它是语言能够"谈论自身"的前提,也是元编程(metaprogramming)的起点。一旦引入,就必须小心处理相等替换。
6.4 学生提问:去掉 quote 会怎样?
- 学生问:如果
+前不加 quote,是否会指向+过程?能否比较两个过程? - 讲师回答:
- 去掉 quote,就指向
+所定义的那个过程; - 确实可以比较两个过程的同一性(identity);
- 但"比较过程"意味着什么,目前并不清楚,甚至有理由认为这毫无意义;
- 而符号是我们能理解的,所以加 quote,谈的是页面上出现的那个符号。
- 去掉 quote,就指向
七、car/cdr 命名趣闻 [17:00]
讲师顺带解释
car和cdr为何沿用至今。
- 历史来源:大约 1958 年,在 IBM 704 机器上,有地址寄存器(address register) 和减量寄存器(decrement register),
car/cdr即"地址寄存器内容/减量寄存器内容"的缩写。 - 为何没被
left/right取代:因为 Lisp 程序员喜欢打电话交流。- 一连串 car/cdr 可以读成
cadaditor这样的词,对方能听懂; - 而"left of right of left"之类就说不清楚了。
- 一连串 car/cdr 可以读成
- 通常列表系统会预定义到四层深的 car/cdr 组合。
八、完整的求导程序与"丑陋"的输出 [19:30]
程序基本完成后,讲师展示运行结果,暴露问题。
8.1 示例
- 定义
foo为ax² + bx + c(用列表结构写成和与积的形式)。 - 对
x求导,得到的结果是一个可怕的、冗长的表达式:- 期望:
2ax + b; - 实际:包含
x·1 + 1·x、0·x²、一堆+0等冗余结构。
- 期望:
- 结果正确,但未化简——讲师说"考试会扣分"。
8.2 过程形态的启示
- 关键观察:我们看到的正是之前讲 Fibonacci 时说过的——过程的形态由过程中的局部规则展开而来。
- 求导过程在表达式这棵树上行走(tree walk),答案的每一部分都对应问题树的某一部分。
- 对
foo分别关于a、b、c求导:- 表达式形状相同;
- 只是 0 和 1 的位置不同;
- 因为求导过程中唯一的"自由度"就是:什么相对于所变变量是常数、什么是同一变量。
- 对和与积的处理没有选择余地,唯一有趣的地方就是判断"是变量还是常数"。
8.3 与有理数算术的类比
- 上一讲有理数的问题:得到
6/8而不是3/4——答案未化简。 - 问题本质相同:有些东西我们希望化简后相同,但实际不相同。
- 而有理数加减乘的规则本身是正确的——问题不在规则,在表示。
九、抽象屏障与表示层改造 [22:30]
讲师提出解决方案:改变表示层,在构造器中内建化简。
9.1 抽象屏障的分解
- 系统被分为几层:
- 求导规则层(derivative rules);
- 代数一般规则层;
- 抽象屏障(abstraction barrier);
- 表示层(列表结构);
- 屏障上暴露接口过程:
constant?、same-variable?、make-sum、a1、a2、make-product等。
- 红色重点:有了抽象屏障,就可以任意更换表示,而不改动上层规则。
9.2 让构造器变聪明
- 改造
make-sum:- 若
a1、a2都是数:直接相加,返回数值; - 若
a1是数且等于 0:返回a2; - 若
a2是数且等于 0:返回a1; - 否则:构造列表
(+ a1 a2)。
- 若
make-product同理(乘 1、乘 0 等情形)。- 理解:这是"智能构造器(smart constructor)"模式——把化简逻辑下沉到表示层,上层规则完全不动。
9.3 改造后的结果
- 对
foo关于x求导:得到2ax + b(基本干净)。 - 关于
a求导:得到x·x; - 关于
b求导:得到x; - 关于
c求导:得到1。 - 注意:仍未完全化简(如未合并同类项)。讲师指出,完整的代数化简是极其复杂的工作,著名的 Macsyma(MIT 的符号代数系统)约 5000 页 Lisp 代码,大部分都是代数化简运算。
易错点:智能构造器只能处理"构造时就能判断"的化简(如
x+0)。真正的化简(合并同类项、提取公因式)需要遍历整个表达式,属于另一层次的复杂问题,不能靠构造器解决。
十、总结:引用的力量与责任 [27:30]
讲师收束全讲,点明本讲真正的"大故事"。
- 本讲用一个精心组织的例子,展示了:
- 如何操作表达式;
- 如何用抽象语法(abstract syntax) 而非具体语法来做这件事;
- 如何利用抽象控制表达式的构造。
- 红色重点:真正的故事是——我操作的这些表达式,正是列表表达式,与语言中表示类似事物的形式相同。
- 这是一个双关(pun):我选择让表示与语言自身的表示一致。
- 由此产生的必然性:语言能够写出"谈论语言自身表达式"的表达式,就必须有
quote来区分"我在谈论这个表达式"与"这个表达式在谈论别的东西"。 - 关键结论:一旦拥有操作语言表达式的能力,就可以在语言之上层层构建更强大的语言——不仅是嵌入 Lisp 的语言,还可以是完全不同、被 Lisp 解释的语言。
- 讲师结语(比喻):我们到达了一条界线,从此获得巨大力量;我们造出了一把大锤(sledgehammer),必须小心选择敲击的对象。
关键标签 / 关键词
符号微分 Symbolic Differentiation 引用 Quotation quote SICP 6.001 MIT Lisp Scheme 抽象屏障 Abstraction Barrier 归约规则 Reduction Rules 递归 Recursion 分情况分析 Case Analysis 类型分派 Dispatch on Type 列表结构 List Structure car/cdr 智能构造器 Smart Constructor 代数化简 Macsyma 同像性 Homoiconicity 指称晦暗语境 Referential Opacity 名称与提及 Name and Mention 谓词命名约定 Predicate Naming Convention
生成时间:2026-10-11 13:59:22 生成平台:VideoNote www.xygy.top