
视频元信息
- 课程:MIT 6.001 Structure and Interpretation of Computer Programs(SICP)1986 年原版
- 主讲:Hal Abelson(本讲由 Harold Abelson 授课)
- 本讲主题:Lisp 导论与课程概览(备选版本)
- 备注:本讲是 6.001 第一讲的一个版本,系统介绍课程三大主题并演示 Lisp 基础语法。
内容摘要(TL;DR)
本讲首先澄清"计算机科学"这个名称的误导性,将计算机科学与几何学类比,指出其本质是形式化"如何做"的过程性知识(imperative knowledge)。随后提出课程三大主题:黑箱抽象、常规接口、元语言抽象。最后完整演示 Lisp 的原始元素、组合手段与抽象手段,包括 define、lambda、cond、if 以及递归定义。
一、计算机科学不是"科学" [00:14]
1.1 名称的误导性
- 开场即指出"computer science"是一个糟糕的名称。
- 重点:它不是科学,可能是工程,也可能是艺术;甚至与魔法(magic)有很多共同之处。
- 它也不是关于计算机的——正如物理学不是关于粒子加速器,生物学不是关于显微镜和培养皿。
1.2 与几何学的类比
- 几何学(geometry)同样是一个"糟糕的名称":源自希腊语 gaia(大地)与 metron(测量),本意是"测量地球"。
- 数千年前,埃及祭司阶层为了在尼罗河每年泛滥后恢复田界,发展出了几何学的雏形。
- 理解:对当时的埃及人来说,几何学确实就是使用测量仪器。 今天我们看他们,会认为真正重要的是他们开始形式化关于空间与时间的观念,开启了公理化方法,通向整个现代数学。
- 推导:我们今天认为计算机科学是关于计算机的,原因和埃及人认为几何学是关于测量仪器的一样——当一个领域刚刚起步、人们尚未真正理解它时,很容易把"所做事情的实质"与"所使用的工具"混淆。
易错点:不要被工具(计算机)迷惑,要关注本质(形式化过程性知识)。 这是全课程的核心态度。
二、声明性知识 vs 过程性知识 [03:56]
2.1 两种知识的对比
- 声明性知识(declarative knowledge):什么是真的(what is true)。
- 过程性知识(imperative knowledge):如何做(how to do)。
2.2 平方根的例子
- 数学定义(声明性):x 的平方根是满足 y² = x 且 y > 0 的数 y。
- 重点:这个定义告诉你"平方根是什么",却完全没告诉你"如何找到一个平方根"。
- 过程性方法(Heron of Alexandria 的逐次平均法):
- 做一个猜测(guess);
- 改进猜测:将猜测值与 x/猜测值 取平均;
- 持续改进直到"足够好"。
理解:这就是"如何做"的知识——一个算法、一个过程。
2.3 过程与过程(procedure)
- 过程(process):可以想象成一种"魔法精灵",听从计算机的指令并执行动作。
- 过程(procedure):指导过程的规则模式,是控制这些魔法精灵的"咒语"。
- 重点:过程(procedure)是咒语,过程(process)是被咒语驱动的精灵。
2.4 Lisp 是"魔法语言"
- 真正的巫师用古埃及语、苏美尔语、巴比伦语等;我们将用一种叫 Lisp 的魔法语言来召唤精灵。
- 理解:Lisp 是"为描述过程、施展控制过程的咒语而设计的语言"。
2.5 学 Lisp 如同学国际象棋规则
- 几分钟就能讲完 Lisp 的全部规则。
- 重点:但学会规则 ≠ 掌握其含义。 就像几分钟能讲完国际象棋规则,但成为大师需要理解规则的推论。
- 本课程余下部分及更远的将来,都在探讨这些规则的推论。
三、计算机科学的真正问题:控制复杂性 [07:21]
3.1 为什么需要控制复杂性
- 计算机科学不是教人算平方根——那没什么大不了。
- 真正的困难出现在构建非常庞大的系统时:程序长达数千页,没人能一次性装进脑子里。
- 之所以可能构建这样的系统,是因为存在控制复杂性的技术。
3.2 与其他工程学科的区别
- 其他工程学科(如大型客机设计)也处理复杂性,但存在关键差异。
- 重点:计算机科学在某种意义上"不是真实的"(isn't real)。
- 物理系统必须处理容差、近似和噪声:
- 例如电气工程师可以造一级、两级放大器,理论上可以级联成百万级放大器;
- 但荒谬——因为前几级的热噪声会被放大,使整个系统失去意义。
- 计算机科学处理的是理想化组件:我们对程序和数据片段了解得和想要的一样精确。
- 推导:因此构建大型程序时,"我能构建的"与"我能想象的"之间差别不大。
关键对比:物理工程的约束来自物理、噪声、近似;软件工程的约束来自我们自身心智的局限。 因此计算机科学是一种"抽象形式的工程"——忽略现实强加的约束。
四、课程三大主题概览 [10:41]
4.1 主题一:黑箱抽象(Black Box Abstraction)
- 所有工程都使用的基本技术:把某物装进一个盒子。
- 以平方根为例:把整个复杂规则集装进"求平方根"的盒子,输入 36,输出 6。
- 重点:盒子内部实现不应对外部使用者重要。 内部用 x、y 还是 a 命名都无所谓。
- 这就是黑箱抽象的核心:抑制细节,以便构建更大的盒子。
4.2 黑箱抽象的第二个理由:表达一般性
- 有时你想表达"你的做法是某个更一般事物的实例"。
- 例子:平方根算法其实是"寻找函数不动点"这一更一般策略的特例。
- 不动点(fixed point)定义:函数 f 的不动点是满足 f(y) = y 的值 y。
- 求不动点的通用策略:从猜测开始,反复应用 f,直到结果不再变化。
- 推导:求 x 的平方根 = 求函数 y ↦ (y + x/y)/2 的不动点。 因为若 y = √x,则 x/y = √x,二者平均不变。
- 重点:我们不仅想表达具体事物(如平方根)的"如何做"知识,还想表达一般事物(如求不动点)的"如何做"知识。
- 这意味着我们要构建能计算方法的盒子——输入是函数,输出是过程。
思考:这就是高阶过程(higher-order procedures)的雏形——过程作为参数和返回值。
4.3 主题二:常规接口(Conventional Interfaces)
- 引入例子:线性组合——把两个东西相加再乘以某个因子。
- 对数字容易,但同样的思想要能用于:
- 向量:a1 + a2 再缩放;
- 多项式:两个多项式相加再乘;
- 电信号:两个信号求和再放大。
- 重点:问题在于——机器中的原始操作对不同类型是不同的。 加两个数、加两个多项式、加两个信号,底层操作完全不同。
- 关键问题:这份知识放在哪里? 如果明天 George 发明了新对象类型,如何把它加入系统而不破坏已有的一切?
- 解决方案:建立常规接口——约定俗成的连接方式,就像电气工程中的标准阻抗连接器。
- 具体内容:
- 通用操作(generic operations):如
+必须能作用于各种数据类型; - 大规模结构:构建模拟现实复杂系统的大型程序,有两种重要隐喻:
- 面向对象编程(object-oriented programming):系统像一个小型社会,各对象通过传递信息交互;
- 流(streams):像信号处理工程师搭建大型电路那样组织系统。
- 通用操作(generic operations):如
4.4 主题三:元语言抽象(Metalinguistic Abstraction)
- 重点:控制复杂性的第三种基本技术是创造新语言。
- 当被设计的复杂性压垮时,选择一种新的设计语言,其目的是:
- 突出系统的不同方面;
- 抑制某些细节,强调另一些细节。
- 这是课程最"魔法"的部分:
- 首先研究构建新计算机语言的技术;
- 用 Lisp 本身来表达 Lisp 的解释器——一个自循环的过程;
- 理解:解释 Lisp 的过程是一个巨大的"apply-eval"轮子,两个过程不断互相归约。
- 还会看到 Y 算子(Y operator)——过程式语言中"无穷"的表达。
- 本节内容:
- 先看解释过程,构建 apply-eval 循环,实现 Lisp;
- 用同样技术构建完全不同的语言——逻辑编程语言(logic programming),不谈有输入输出的过程,而谈事物之间的关系;
- 最后讨论如何在最简单的机器上具体实现——甚至包括 Lisp 解释器芯片的硬件实现。
五、学习任何语言的通用框架 [27:50]
5.1 三个关键问题
当有人向你展示一门新语言时,不要问"求矩阵逆需要几个字符",而要问:
- 原始元素(primitive elements):语言自带什么?
- 组合手段(means of combination):如何把原始元素组合成更大的东西?
- 抽象手段(means of abstraction):如何给复杂事物命名、装箱,使其能像原始元素一样使用?
考点:这三个问题(primitives / means of combination / means of abstraction)是本课程分析任何语言的核心框架,务必牢记。
理解:这个框架贯穿整门课——从 Lisp 到逻辑编程,再到硬件实现,都在反复运用这一分析结构。
六、Lisp 的原始元素 [29:55]
6.1 原始数据
- 数字 3:严格说不是数字 3,而是代表数字 3 概念的符号。
- 17.4:某个表示 17.4 的对象。
- 5:另一个原始对象。
6.2 原始过程
+:加法的名称,就像 3 是数字三的名称一样。- 重点:
+是"如何相加"这一概念的命名。
6.3 组合与求值
- 表达式:
(+ 3 17.4 5),求值结果为 25.4。 - 组合(combination):由操作符(operator)作用于若干操作数(operand)构成。
- 操作数本身可以是组合,因此可以产生复杂性:
(+ 3 (* 5 6) 8 2)→ 43。
七、Lisp 的语法:前缀表示法与括号 [33:25]
7.1 前缀表示法(prefix notation)
- 操作符写在操作数左边,这只是一种约定。
- 重点:完全括号化(fully parenthesized),使表达式毫无歧义。
7.2 括号的严格性
- 数学中括号表示分组,多写少写通常无伤大雅。
- Lisp 中不能省略括号,也不能多加括号。
- 易错点:加括号永远精确地意味着"这是一个组合"——即"把操作符作用于操作数"。 去掉或增加括号会改变含义。
7.3 树形结构
- 组合本质上是一棵树:
- 根是
+,子节点是 3、(* 5 6)、8、2; (* 5 6)自身是一棵小子树。
- 根是
- 理解:括号只是把二维树结构写成线性字符流的方式。 如果 Lisp 诞生于今天,语法可能直接画成树。
八、Lisp 交互演示 [37:00]
8.1 基本求值
- 输入符号 3,Lisp 返回 3。
- 输入
(+ 3 4 8),返回 15。 - 输入嵌套组合
(+ (* 3 (+ 7 19.5)) 4),演示括号匹配。
8.2 括号辅助工具
- 闪烁括号匹配:输入右括号时,对应的左括号会闪烁。
- 自动缩进 / 美化打印(pretty printing):编辑器自动把操作数垂直对齐到正确缩进位置。
- 重点:这两种工具在多数 Lisp 系统中内置,应学会使用,因为手工跟踪括号很困难。
九、抽象手段:define 与命名 [44:35]
9.1 最简单的命名
(define a (* 5 5)):把a命名为 25。- 求值
(* a a)→ 625。 - 可以继续用
a定义b:(define b (+ a (* 5 a)))。 - 输入定义时,Lisp 返回被定义的符号名。
9.2 命名一般方法:过程定义
- 更强大的是命名一般方法,而非单个值。
- 想命名"把某物乘以自身"这一一般思想——即"平方"。
(define (square x) (* x x)):x是一个"代词",代表要平方的那个东西;- 求值
(square 10)→ 100。
9.3 lambda:过程定义的底层形式
- 另一种写法:
(define square (lambda (x) (* x x)))。 - 重点:lambda 意为"构造一个过程":
- 参数名为 x;
- 过程体返回 x 乘以自身的结果。
- 语法糖(syntactic sugar):
(define (square x) ...)只是(define square (lambda (x) ...))的便捷表面形式。 - 易错点:不要忘记——写
(define (square x) ...)时,你真正命名的是 square,而它被绑定到一个构造出来的过程上。
十、过程作为一等公民 [49:30]
10.1 演示
(square 1001)、(square (+ 5 7))→ 144。(+ (square 3) (square 4))→ 25。(square (square (square 1001))):嵌套使用。- 直接输入
square,Lisp 返回compound procedure square及内部表示。
10.2 关键原则:原始与复合无差别
(define (average x y) (/ (+ x y) 2))。(define (mean-square x y) (average (square x) (square y)))。- 求值
(mean-square 2 3)→ 6.5。 - 重点:定义 mean-square 的人无需知道 square 是语言内置的还是用户定义的。
- Lisp 不区分语言中原始的东西和用户构建的东西——你构建的东西享有与原始元素同等的威力和灵活性。
- 演示:输入
+,Lisp 返回compound procedure +——说明加法在本系统中本身就是复合过程。 - 理解:你无法(也不应能)分辨内置与复合的区别,因为复合的东西外面包了一层抽象包装。
考点:这是 Lisp 的核心设计哲学——消除原始与复合的人为区分,是黑箱抽象的必然结果。
十一、条件分析:cond 与 if [56:00]
11.1 绝对值的数学定义
- |x| = -x(x < 0);0(x = 0);x(x > 0)。
11.2 cond 形式
(define (abs x) (cond ((< x 0) (- x)) ((= x 0) 0) ((> x 0) x)))。- 条件子句(clause) 由两部分组成:
- 谓词(predicate):返回真或假的表达式,如
(< x 0); - 动作(action):条件为真时执行的操作。
- 谓词(predicate):返回真或假的表达式,如
- 重点:谓词(predicate)是返回真或假的东西。
- 注意
-的歧义:- 两个参数:减法;
- 一个参数:取负。
11.3 if 形式
(define (abs x) (if (< x 0) (- x) x))。- 重点:if 是受限的条件分析,只有一个测试,真则做一件事,假则做另一件。
- 理解:cond 和 if 互为语法糖——实现者选一个实现另一个,二者本质相同。
11.4 关于 define 的两种形式(答疑)
- 提问:为什么有时
define后跟括号,有时不跟? - 回答:
- 基本形式是
(define <符号> <值>)——第二个位置写符号,不写括号; (define (square x) ...)是定义过程的特殊语法技巧;- 重点:区别在于是否在定义过程。
- 基本形式是
十二、递归:以 Heron 平方根算法为例 [01:04:00]
12.1 算法回顾
- 要逼近 x 的平方根:做猜测 g,用 (g + x/g)/2 改进,直到足够好。
12.2 收敛性论证
- 若 g = √x,则 x/g = √x,平均不变。
- 若 g > √x,则 x/g < √x,平均值落在两者之间。
- 若 g < √x,则 x/g > √x,平均值同样落在两者之间。
- 推导:可以证明,若 g 与 √x 有微小偏差,平均值会更接近 √x。 且总可以用 1 作为初始猜测,必定收敛。
12.3 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))。- 重点:try 的定义中引用了 try 自身——这是递归定义。
12.4 递归的展开过程
- 求
(sqrt 2):- try 1 → 1 不够好 → try (improve 1) = try 1.5;
- try 1.5 → 不够好 → try 1.333...;
- 持续归约,直到 good-enough? 判定足够好,得到约 1.4142。
- 理解:过程完全合理。高中几何老师说过"用自身定义自身是不对的"——但那是错的。有时用自身定义自身完全合理。
- 重点:递归定义是巨大威力的来源——它让你能进行"持续到条件为真才停止"的无限计算,而无需任何其他构造(如 for、while)。
考点:递归(recursion)是 Lisp 中实现迭代和循环的核心机制,本课程不会使用 for/while/do-until 等语句。
十三、块结构(Block Structure) [01:12:00]
13.1 封装内部过程
- 把
improve、good-enough?、try的定义打包进sqrt内部:(define (sqrt x) (define (improve guess) (average guess (/ x guess))) (define (good-enough? guess) (< (abs (- (square guess) x)) 0.001)) (define (try guess) (if (good-enough? guess) guess (try (improve guess)))) (try 1)) - 重点:这就是"构建一个平方根盒子"——使用者输入 36 得到 6,内部细节被隐藏。
- 理解:George 使用 sqrt 时,不关心内部有 try、good-enough?、improve。
- 重点:Harry 可能也有 cube-root 过程,内部同样有 try、good-enough?、improve——把它们封装在各自过程内,可避免整个系统混乱。
- 这叫块结构(block structure)——在定义内部打包内部过程的特殊方式。
- 理解:由于都在 sqrt 内部,try 无需显式接收 x 参数,x 自然可见(词法作用域)。
十四、本讲总结与后续展望 [01:16:00]
14.1 已学内容
- 原始元素:
+、*等;谓词如<、=;原始数据如数字。- 注:实际系统中这些未必是真正的原始元素,但重要的是我们"当作原始元素使用",不窥视内部。
- 组合手段:组合函数、用操作符和操作数构建组合;cond、if、define。
- 抽象手段:
define是命名事物的方式。
14.2 待填补的空白
- 如何组合原始数据得到复合数据(compound data);
- 如何抽象数据,使大块数据能像原始元素一样使用。
14.3 后续讲座预告
- 首先:过程与机器中实际发生的过程之间的联系(即求值模型/替换模型);
- 然后:利用 Lisp 的力量谈论一般的常规方法,而非个别小计算。
十五、答疑:define 加括号与不加括号的区别 [01:20:00]
- 提问:若把
(define a (* 5 5))写成(define (a) (* 5 5))有何区别? - 回答:
(define (a) (* 5 5))定义的是名为a的无参数过程;- 输入
a,Lisp 返回 25(因为a是值); - 输入
d(若d是过程),Lisp 返回compound procedure d; - 运行
(d)(无操作数的组合),返回 25; - 而输入
(a)会报错:"25 不是一个可以作用于某物的操作符"。
- 易错点:区分"命名一个值"与"命名一个过程"——前者直接求值,后者需要调用才产生值。
关键标签 / 关键词
SICP #MIT6001 #Lisp #黑箱抽象 #常规接口 #元语言抽象 #过程性知识 #声明性知识 #递归 #不动点 #lambda #cond #if #define #块结构 #语法糖 #高阶过程 #控制复杂性 #Heron算法 #前缀表示法 #抽象屏障
生成时间:2026-10-11 17:12:05 生成平台:VideoNote www.xygy.top