
视频元信息
- 课程名称:6.001 计算机程序的构造与解释(Structure and Interpretation of Computer Programs, SICP)
- 授课教师:Hal Abelson(MIT)
- 年份:1986年(传奇原版)
- 本讲主题:Lisp 导论与课程概览
内容摘要(TL;DR)
本讲是 SICP 课程的开篇。Abelson 教授首先批判"计算机科学"这个名称,指出其本质是形式化"如何做"的过程性知识(imperative knowledge),而非关于计算机本身。随后介绍课程三大主题:黑盒抽象、约定接口、元语言抽象,并以 Lisp 语言为例讲解原始元素、组合手段与抽象手段,最后通过牛顿法求平方根引出递归定义。
一、计算机科学是一门"名不副实"的学科 [00:14]
1.1 为什么"计算机科学"是个糟糕的名字
Abelson 开篇即指出:"计算机科学"(computer science)是一个糟糕的名称。
- 它不是科学(science):可能更像工程(engineering)或艺术(art),甚至与魔法(magic)有很多共同点。
- 它也不是关于计算机的:就像物理学不是关于粒子加速器的,生物学不是关于显微镜和培养皿的。
理解:这是一个非常重要的认识论问题。学科的名称往往来源于它最初使用的工具,而非它的本质。
1.2 几何学的类比
考点:几何学(geometry)同样是一个"糟糕的名字"。
- 词源:geo(大地)+ metron(测量)= 测量大地/测量术
- 历史背景:几千年前,埃及祭司阶层为了在尼罗河每年泛滥后恢复被冲毁的田界,发展了几何学的雏形。
- 对当时的埃及人来说,几何学真的就是使用测量仪器。
推导:今天我们回望埃及人,会说他们真正做的、重要的事情,是开始形式化关于空间和时间的概念,开创了精确谈论数学真理的方式,最终导向了公理化方法和整个现代数学。
1.3 计算机科学的本质
重点:计算机科学的本质是形式化"过程性直觉"(intuitions about process)。
- 几何学谈论的是"什么是真的"(declarative knowledge,声明性知识)
- 计算机科学谈论的是"如何做"(imperative knowledge,过程性知识)
易错点:我们之所以认为计算机科学是关于计算机的,和埃及人认为几何学是关于测量仪器的,原因完全相同——当一个领域刚刚起步、人们还没真正理解它时,很容易把做事的本质与所用的工具混淆。Abelson 甚至说,在某种绝对尺度上,我们对计算机科学本质的了解,可能还不如古埃及人对几何学本质的了解。
二、声明性知识 vs 过程性知识 [03:57]
2.1 平方根的例子
声明性知识(What is true)——数学上对平方根的定义:
x 的平方根是满足 y² = x 且 y > 0 的数 y。
这是一个漂亮的数学陈述,但它只告诉你平方根是什么,完全没有说如何找到它。
过程性知识(How to do)——如何求平方根:
- 来源:亚历山大城的海伦(Heron of Alexandria)的算法
- 方法:逐次平均法(successive averaging)
- 做一个猜测(make a guess)
- 改进这个猜测:将猜测值与 x/猜测值 取平均
- 不断改进,直到"足够好"(good enough)
2.2 过程、程序与语言
- 过程(process):可以想象成一种"魔法精灵",听从计算机的指令去做事。
- 程序(procedure):指导过程的规则模式,即"控制魔法精灵的咒语"。
- Lisp:我们用来"念咒"的魔法语言。
思考:Abelson 用"魔法"作比喻并非随意。过程是看不见、摸不着的抽象存在,程序则是描述过程的符号模式——这正是 SICP 反复强调的"过程"与"程序"的区分。
2.3 学习 Lisp 的类比
重点:Lisp 的规则几分钟就能学完,但掌握其含义需要整门课。
- 类比下棋:几分钟就能讲完国际象棋的规则,但这与"理解规则的推论、成为大师级棋手"完全不同。
- 本课程真正难的部分:不是规则本身,而是规则的推论(implications)——如何利用这些规则成为大师级程序员。
三、计算机科学的真正问题:控制复杂性 [07:21]
3.1 大系统的挑战
考点:计算机科学的真正问题出现在构建超大型系统时。
- 程序可能长达数千页,长到没有人能一次性装进脑子里。
- 之所以可能构建这样的系统,唯一原因是存在控制复杂性的技术。
- 这些技术正是本课程的核心内容,也是计算机科学的本质。
3.2 与其他工程学科的关键区别
有人会说:大型客机也是极其复杂的系统,航空工程师同样在处理巨大的复杂性。但计算机科学的复杂性与物理工程有本质区别:
| 维度 | 物理工程(如电子工程) | 软件工程 |
|---|---|---|
| 组件 | 真实物理部件 | 理想化抽象组件 |
| 关注点 | 容差、近似、噪声、干扰 | 无需担心容差 |
| 约束 | 物理定律、时间、噪声 | 人类心智的局限 |
易错点:百万级放大器的例子——电子工程师可以轻松搭建一级、二级放大器,但想象搭建百万级放大器是荒谬的,因为前几级组件的噪声会被逐级放大,使整个系统失去意义。
推导:但计算机科学处理的是理想化组件——我们对程序和数据片段了解得和想要的一样多,它们精确如我们所愿。因此,构建大型程序时,"我能构建的"和"我能想象的"之间差别不大。
结论:计算机科学是一种抽象形式的工程——一种忽略现实约束的工程。
四、课程三大主题概览 [10:38]
4.1 第一主题:黑盒抽象(Black Box Abstraction)
这是所有工程都使用的技术。
- 把某个东西装进"盒子",对外只暴露接口。
- 例如:把求平方根的方法装进一个盒子,输入 36 输出 6。
- 关键:George 想计算 √a + √b 时,可以直接把"平方根盒子"当模块用,无需知道盒子内部。
- 从 George 的角度看,盒子内部实现无关紧要——我写的是 √x 还是 √y 还是 √a,都不重要。
黑盒抽象的第二个理由:有时你想表达"你的方法"是某个更一般概念的特例,并希望语言能表达这种一般性。
4.2 不动点(Fixed Point)——一般化策略的例子
重点:平方根算法是一般"不动点"策略的特例。
- 不动点定义:函数 F 的不动点是一个值 y,使得 F(y) = y。
- 一般策略:从猜测开始,反复应用 F,直到结果不再变化。
- 平方根是不动点:求 √x 可以转化为求函数
y → (y + x/y)/2的不动点。- 推导:如果 y = √x,那么 x/y = √x,两者相等,平均值不变,即 F(y) = y。
关键洞察:我们不仅要表达"如何求平方根"这种具体过程性知识,还要表达"如何求不动点"这种一般过程性知识。
思考:这意味着"盒子"不仅能输入数字输出数字,还能输入函数、输出方法。这就是高阶过程(higher-order procedures)的雏形——输入和输出本身是过程的过程。
4.3 第二主题:约定接口(Conventional Interfaces)
问题:如何表达"线性组合"这样的一般概念?
- 对数字:
2 × (1 + 3) = 8 - 对向量:
x × (a1 + a2) - 对多项式:两个多项式相加再乘以系数
- 对电信号:两个信号相加再放大
易错点:不同数据类型(数字、多项式、电信号)的底层加法操作显然不同。知识应该放在哪里? 如果明天 George 带来一种新对象,如何把它加入系统而不破坏已有的一切?
解决方案:建立约定接口(conventional interfaces)——就像电子工程中标准阻抗的连接器,只要遵循标准接口,就能互相插接。
第二主题包含的内容:
- 通用操作(generic operations):如
+必须能处理所有类型的数据。 - 大型结构:如何组织模拟现实复杂系统的大型程序。
- 面向对象编程(object-oriented programming):把系统看作"小对象的社会",对象间通过发送信息交互。
- 流(streams):对聚合体进行操作,像信号处理工程师搭建大型电路那样组织系统。
4.4 第三主题:元语言抽象(Meddling with the Abstraction)
考点:控制复杂性的第三种技术——创造新语言。
- 当被设计的复杂性压垮时,选择一种新的设计语言来控制复杂性。
- 新语言的目的:突出系统的不同方面,抑制某些细节,强调另一些细节。
- 这是课程最"魔法"的部分。
本主题的内容:
- 构建 Lisp 解释器:用 Lisp 本身表达 Lisp 的解释过程(自指涉,self-referential)。
- 理解:解释 Lisp 的过程是两个过程——
eval(求值)和apply(应用)——不断相互归约的"巨大循环"。
- 理解:解释 Lisp 的过程是两个过程——
- Y 算子(Y operator):在过程性语言中表达"无穷"的符号。
- 逻辑程序设计语言(logic programming):用完全相同的技术构建一种截然不同的语言,不谈有输入输出的过程,只谈事物之间的关系(relations)。
- 硬件实现:在最简单的机器上具体实现这些系统——包括用硬件实现的 Lisp 解释器芯片。
五、语言的通用分析框架 [23:08]
重点:这是本课程最重要的东西——分析任何语言的通用框架。
当有人向你介绍一门"很棒的新语言"时,不要问"求逆矩阵要敲多少个字符"(这无关紧要),而应该问三个问题:
- 原始元素(primitive elements):语言自带什么?
- 组合手段(means of combination):如何把原始元素组合成更大的东西?
- 抽象手段(means of abstraction):如何给复杂的东西命名,使其能像原始元素一样被使用?
思考:这个"三问框架"是 SICP 的核心方法论,贯穿整门课程。它把语言学习从"记忆语法"提升到"理解结构"的层次。
六、Lisp 的原始元素 [24:44]
6.1 原始数据
- 数字:如
3、17.4、5 - 易错点(哲学层面的严谨):Abelson 强调,
3不是数字三本身,而是代表柏拉图式"数字三"概念的一个符号。
6.2 原始过程
- 加法:
+是"柏拉图式加法概念"的一个名称,如同3是数字三的名称。
理解:这种"符号 vs 所指"的区分看似吹毛求疵,但它是理解 Lisp 元语言能力的基础——语言可以谈论语言自身。
七、组合手段:组合式(Combinations)[25:35]
7.1 基本形式
(+ 3 17.4 5) → 25.4
组合式(combination) 的构成:
- 操作符(operator):如
+ - 操作数(operands):如
3、17.4、5
7.2 嵌套组合
(+ 3 (* 5 6) 8 2) → 43
重点:操作数本身可以是组合式——这是复杂性的来源。
7.3 前缀表示法(Prefix Notation)
- 操作符写在操作数左边
- 完全括号化(fully parenthesized),使表达完全无歧义
7.4 括号的严格性
易错点:Lisp 的括号与数学中的括号完全不同。
- 数学中括号表示分组,有时可以省略,多加也无妨。
- Lisp 中不能省略括号,也不能多加括号——每个括号都精确地表示"这是一个组合式"。
- 去掉括号,含义就变了。
7.5 树形结构视角
理解:组合式本质上是一棵树。
- 括号只是把二维的树结构写成线性字符串的方式。
- 例如
(+ 3 (* 5 6) 8 2)的树:- 根节点:
+ - 子节点:
3、(* 5 6)子树、8、2 (* 5 6)子树:根为*,子为5、6
- 根节点:
思考:Abelson 提到,如果 Lisp 诞生于今天,有了图形界面,语法可能会直接画成树。括号是历史妥协的产物。
八、Lisp 解释器实操演示 [29:23]
8.1 基本求值
- 输入
3→ 返回3 - 输入
(+ 3 4 8)→ 返回15 - 输入嵌套组合 → 返回对应值
8.2 括号辅助功能
重点:许多 Lisp 系统内置两种括号辅助功能:
- 括号闪烁(flashing parentheses):输入右括号时,高亮匹配的左括号,帮助追踪括号配对。
- 自动缩进 / 美化打印(pretty printing):编辑器自动缩进到正确层级,使嵌套结构一目了然。
易错点:手工处理大量括号很容易出错,必须学会使用这些辅助功能。
九、抽象手段:define [34:28]
9.1 命名值
(define a (* 5 5))
- 现在
(* a a)返回 625 a可以在更复杂的组合中使用
(define b (+ a (* 5 a)))
- 定义时,解释器返回被定义的符号(如
a、b)
9.2 命名过程(一般方法)
重点:我们真正想命名的是"一般方法",如"平方"。
(define (square x) (* x x))
- 这里
x是一个代词(pronoun),代表"要被平方的东西"。 (square 10)→100
9.3 lambda:define 的底层形式
(define square (lambda (x) (* x x)))
考点:lambda 是"制造过程"的方式。
- 读法:"定义 square 为:制造一个过程,其参数名为 x,它返回 x 乘以 x 的结果。"
- 两种写法对 Lisp 解释器而言完全等价。
9.4 语法糖(Syntactic Sugar)
重点概念:语法糖 = 更方便的表面书写形式。
(define (square x) ...)是(define square (lambda (x) ...))的语法糖。- 易错点:不要忘记,写
(define (square x) ...)时,你实际上是在给某个东西命名,而那个东西是一个正在被构造的过程。
9.5 过程作为一等公民
关键:已定义的过程可以像原始元素一样使用。
(square 1001)、(square (+ 5 7))(+ (square 3) (square 4))→25(square (square (square 1001)))嵌套使用- 直接求值
square→ 返回#<compound procedure square>(过程的打印表示)
9.6 平均值与均方
(define (average x y) (/ (+ x y) 2))
(define (mean-square x y) (average (square x) (square y)))
(mean-square 3 4)→(average 9 16)→6.5- 重点:定义
mean-square的人无需知道square是语言内置的还是用户定义的。
核心原则:Lisp 不在"语言内置的东西"和"用户构建的东西"之间做任意区分。用户构建的东西拥有与原始元素完全相同的威力和灵活性。
演示:在系统中查询
+的值,返回#<compound procedure +>——连加法操作符本身都是复合过程!但你永远不会知道,也不应该知道,因为它处于我们处理的抽象层次之下。
十、条件表达式:cond 与 if [43:47]
10.1 绝对值函数的数学定义
|x| = -x, 当 x < 0
= 0, 当 x = 0
= x, 当 x > 0
10.2 用 cond 实现
(define (abs x)
(cond ((< x 0) (- x))
((= x 0) 0)
((> x 0) x)))
结构说明:
- cond = 条件表达式(conditional),表示情况分析。
- 每个子句(clause) 有两部分:
- 谓词(predicate):返回真或假的表达式,如
(< x 0) - 动作(action):条件为真时执行的操作,如
(- x)
- 谓词(predicate):返回真或假的表达式,如
易错点:Lisp 中减号的两种含义:
- 两个参数:
(- a b)→ 减法(从 a 减 b) - 一个参数:
(- x)→ 取负(negation)
10.3 用 if 实现
(define (abs x)
(if (< x 0) (- x) x))
重点:if 是受限的情况分析(只有一真一假两个分支)。
理解:
cond和if本质相同,可以互相定义。实现 Lisp 系统的人会选一个,用另一个来实现它,选哪个都无所谓。
十一、递归定义:牛顿法求平方根 [50:39]
11.1 海伦算法的数学原理
重点:逐次平均法为什么有效?
- 如果猜测值 G 恰好等于 √x,那么 x/G = √x,平均值不变。
- 如果 G 大于 √x,则 x/G 小于 √x,平均值落在两者之间。
- 如果 G 小于 √x,则 x/G 大于 √x,平均值同样落在两者之间。
- 推导:可以证明,如果 G 与 √x 有偏差,G 与 x/G 的平均值会更接近 √x。反复迭代即可逼近。
- 另一个事实:总是可以用 1 作为初始猜测,且总能收敛到 √x。
11.2 用 Lisp 实现
(define (try guess x)
(if (good-enough? guess x)
guess
(try (improve guess x) x)))
(define (sqrt x)
(try 1 x))
(define (improve guess x)
(average guess (/ x guess)))
(define (good-enough? guess x)
(< (abs (- (square guess) x)) 0.001))
11.3 模块化结构
sqrt定义在try之上try定义在good-enough?和improve之上good-enough?定义在abs和square之上improve定义在average和原始操作符之上try还定义在自身之上(递归)
11.4 递归定义展开演示
考点:递归定义如何逐步归约。
求 √2:
(try 1 2)→ 1 不够好 →(try (improve 1 2) 2)(improve 1 2)=(average 1 (/ 2 1))=(average 1 2)=1.5(try 1.5 2)→ 1.5 不够好 →(try (improve 1.5 2) 2)(improve 1.5 2)=(average 1.5 1.333)=1.333(try 1.333 2)→ 继续...- 最终收敛到约
1.414
易错点:高中数学老师可能告诉你"用自身定义自身是不对的",但这是错误的!有时用自身定义自身完全合理,这就是递归定义(recursive definition)。
重点:递归定义是巨大威力的来源,它实际上让你能进行"无穷计算"。
11.5 内部定义与封装
重点:可以通过内部定义把辅助过程(improve、good-enough?、try)封装在 sqrt 内部。
- 这样外界看不到这些辅助过程,只看到
sqrt这个黑盒。 - 这是黑盒抽象的体现:把内部实现细节打包隐藏在定义内部。
十二、课程内容总结与回顾 [57:48]
12.1 已学到的 Lisp 元素
| 类别 | 内容 |
|---|---|
| 原始过程 | +、*、谓词(<、=)等 |
| 原始数据 | 数字 |
| 组合手段 | 组合式(操作符 + 操作数)、cond、if、define |
| 抽象手段 | define(命名)、lambda |
理解:Abelson 指出,我们用的这些"原始过程"实际上并非真正的原始元素,但这无关紧要——我们将其当作原始元素使用,不去看内部。
12.2 待填补的空白
重点:目前只处理了原始数据,还未涉及:
- 如何把原始数据组合成复合数据(compound data)
- 如何对数据进行抽象,使大块数据能像原始元素一样使用
12.3 后续课程预告
接下来的几讲将讨论:
- 我们写的过程与机器中发生的过程之间的联系(即求值模型)。
- 如何用 Lisp 的威力谈论一般的约定方法,而不仅是单个小计算。
十三、问答环节 [59:39]
Q1:define 有时带括号((define (f x) ...)),有时不带((define a ...)),区别是什么?
答:
(define a ...)是基本形式:define后面跟符号,再跟该符号所代表的值。(define (square x) ...)是定义过程的特殊语法技巧(语法糖)。- 区别在于是否在定义过程。
Q2:如果 a 用括号定义会怎样?
答:(define (a) (* 5 5)) 定义的是一个名为 a 的过程(无参数),调用它才返回 25。
对比演示:
| 操作 | (define a (* 5 5)) |
(define (d) (* 5 5)) |
|---|---|---|
输入 a / d |
返回 25 |
返回 #<compound procedure d> |
输入 (a) |
报错:25 不是可应用的操作符 | 返回 25 |
思考:这个对比清晰地展示了"值"与"过程"的区别,以及
define两种形式的本质差异。
关键标签 / 关键词
SICP Lisp 黑盒抽象 约定接口 元语言抽象 声明性知识 过程性知识 组合式 前缀表示法 define lambda 语法糖 cond if 递归定义 牛顿法 海伦算法 不动点 高阶过程 通用操作 面向对象 流 解释器 eval apply Y算子 逻辑程序设计 原始元素 组合手段 抽象手段 控制复杂性 MIT 6.001
生成时间:2026-10-11 13:52:05 生成平台:VideoNote www.xygy.top