Lab 9: Scheme | CS 61A 2024春季课程实验
Lab 9: Scheme
截止时间:4月3日 星期三 晚上11:59
起始文件
下载 lab09.zip。在压缩包内,你将找到本次实验的起始文件,以及 Ok 自动评分器的副本。
Scheme 简介
61A Scheme 解释器已包含在每个 Scheme 作业中。要启动它,请在终端中键入:python3 scheme。要加载名为 f.scm 的 Scheme 文件,请键入:python3 scheme -i f.scm。要退出 Scheme 解释器,请键入 (exit)。
Scheme 编辑器
所有 Scheme 作业都提供一个基于 Web 的编辑器,方便运行 ok 测试并可视化环境。在终端输入python3 editor后,编辑器将在浏览器窗口中打开,地址为http://127.0.0.1:31415/。要停止运行编辑器并返回命令行,请在启动编辑器的终端按下Ctrl-C键。
“运行”按钮会加载当前作业的 .scm 文件,并启动 Scheme 解释器,方便你尝试运行不同的 Scheme 表达式。
“测试”按钮会运行该作业的所有 ok 测试。对于失败的测试,可以点击“查看用例”,然后点击“调试”来逐步执行。
推荐的 VS Code 扩展
如果你选择使用 VS Code 作为文本编辑器(而非网页编辑器),建议安装 vscode-scheme 扩展,它可以高亮显示括号。
之前:

之后:

作业题目
入门指导视频
这些视频能帮助你更好地解决本次作业中的编程问题。
要观看这些视频,请使用你的 berkeley.edu 邮箱账号登录。
可以直接开始做题,遇到困难时再回头参考这些内容。
原子表达式(也称作原子)是指不包含子表达式的表达式,例如数字、布尔值和符号。
scm> 1234 ; 整数
1234
scm> 123.4 ; 实数
123.4
scm> #f ; Scheme 中等价于 Python 中的 False
#f
Scheme 符号 相当于 Python 中的变量名。符号会被解析为当前环境中该符号所绑定的值。之所以称它们为符号而不是变量名,是因为它们可以包含 + 等算术符号。
scm> quotient ; 绑定到内置过程的符号
#[quotient]
scm> + ; 绑定到内置过程的符号
#[+]
在 Scheme 中,除了 #f(等价于 Python 中的 False)之外,所有其他值都被视为真值。
scm> #t
#t
scm> #f
#f
Scheme 使用波兰前缀表达式,运算符位于操作数之前。例如,要计算 3 * (4 + 2),我们写成:
scm> (* 3 (+ 4 2))
18
与 Python 类似,计算函数调用表达式时:
- 先计算运算符,其结果应该是一个过程(procedure)。
- 从左到右计算操作数。
- 将该过程应用于计算后的操作数。
以下是一些使用内置过程的示例:
scm> (+ 1 2)
3
scm> (- 10 (/ 6 2))
7
scm> (modulo 35 4)
3
scm> (even? (quotient 45 2))
#t
Define(定义): define 形式用于将值赋给符号。 语法如下:
(define <符号> <表达式>)
scm> (define pi (+ 3 0.14))
pi
scm> pi
3.14
计算 define 表达式时:
- 计算最终子表达式 (
<表达式>),此例中结果为3.14。 - 将该值绑定到符号 (
symbol),此例中为pi。 - 返回该符号。
define 形式还可以定义新的函数,详见“定义函数”部分。
If 表达式: if 特殊形式基于条件判断来计算两个表达式之一。
(if <条件> <if-true> <if-false>)
计算 if 特殊形式表达式的规则如下:
- 计算
<条件>。 - 如果
<条件>的计算结果为真值(除了#f之外的任何值),则计算并返回<if-true>表达式的值。 否则,计算并返回<if-false>表达式的值。
例如,即使子表达式 (/ 1 (- x 3)) 在计算时会出错,此表达式也不会出错,并且计算结果为 5。
scm> (define x 3)
x
scm> (if (> (- x 3) 0) (/ 1 (- x 3)) (+ x 2))
5
<if-false> 表达式是可选的。
scm> (if (= x 3) (print x))
3
让我们比较一下 Scheme if 表达式和 Python if 语句:
- Scheme
- Python
scm> (if (> x 3) 1 2)
>>> if x > 3:
... 1
... else:
... 2
Scheme if 表达式的计算结果为一个数字(1 或 2,取决于 x)。 Python 语句的计算结果没有任何内容,因此 1 和 2 无法使用或访问。
两者之间的另一个区别是,可以在 Python if 语句的代码块中添加更多代码行,而 Scheme if 表达式期望在 <if-true> 和 <if-false> 位置中只有一个表达式。
最后一个区别是,在 Scheme 中,您不能编写 elif 子句。
Cond 表达式: cond 特殊形式可以包含多个条件判断(类似于 Python 中的 if/elif):
(cond
(<p1> <e1>)
(<p2> <e2>)
...
(<pn> <en>)
(else <else-expression>))
每个子句中的第一个表达式是一个条件。 子句中的第二个表达式是与其条件对应的返回表达式。 else 分支是可选的; 如果没有一个条件为真,则其 <else-expression> 是返回表达式。
计算规则如下:
- 按顺序计算条件
<p1>、<p2>、...、<pn>,直到其中一个计算结果为真值(除了#f之外的任何值)。 - 计算并返回与第一个真值条件对应的返回表达式的值。
- 如果没有一个条件的计算结果为真值并且存在
else分支,则计算并返回<else-expression>。
例如,以下 cond 表达式返回 x 离 3 的倍数最近的值:
scm> (define x 5)
x
scm> (cond ((= (modulo x 3) 0) x)
((= (modulo x 3) 1) (- x 1))
((= (modulo x 3) 2) (+ x 1)))
6
Lambda 表达式 (Lambdas): lambda 特殊形式用于创建函数 (hánshù)。
(lambda (<param1> <param2> ...) <body>)
这个表达式将会创建并返回一个带有指定形参和函数主体的函数,类似于 Python 中的 lambda 表达式。
scm> (lambda (x y) (+ x y)) ; 返回一个 lambda 函数,但没有将其赋值给任何名称
(lambda (x y) (+ x y))
scm> ((lambda (x y) (+ x y)) 3 4) ; 创建并调用一个单行的 lambda 函数
7
以下是等价的 Python 表达式:
>>> lambda x, y: x + y
<function <lambda> at ...>
>>> (lambda x, y: x + y)(3, 4)
7
<body> 可以包含多个表达式。Scheme 函数会返回函数体中最后一个表达式的计算结果。
define 形式可以创建一个函数并为其命名:
(define (<symbol> <param1> <param2> ...) <body>)
例如,以下是如何定义 double 函数:
scm> (define (double x) (* x 2))
double
scm> (double 3)
6
这是一个带有三个参数的示例:
scm> (define (add-then-mul x y z)
(* (+ x y) z))
scm> (add-then-mul 3 4 5)
35
当对 define 表达式求值时,会发生以下步骤:
- 创建一个带有给定参数和
<body>的函数。 - 将该函数绑定到当前帧中的
<symbol>。 - 返回
<symbol>。
以下两个表达式是等价的:
scm> (define add (lambda (x y) (+ x y)))
add
scm> (define (add x y) (+ x y))
add
Q1: 超过或未达到 (Over or Under)
定义一个函数 over-or-under,它接受一个数字 num1 和一个数字 num2,并返回以下内容:
- 如果
num1小于num2,则返回 -1 - 如果
num1等于num2,则返回 0 - 如果
num1大于num2,则返回 1
挑战:使用
if和cond以两种不同的方式实现它!
(define (over-or-under num1 num2)
'YOUR-CODE-HERE
)
使用 Ok 来测试你的代码:
python3 ok -q over_or_under
Q2: 创建加法器 (Make Adder)
编写函数 make-adder,它接受一个初始数字 num,然后返回一个函数。 这个返回的函数接受一个数字 inc,并返回 num + inc 的结果。
提示 (tíshì):要返回一个函数,你可以返回一个
lambda表达式或者define另一个嵌套函数。请记住,Scheme 会自动返回你函数中的最后一个子句。你可以在 61A scheme 规范 中找到关于
lambda表达式语法的文档!
(define (make-adder num)
'YOUR-CODE-HERE
)
使用 Ok 来测试你的代码:
python3 ok -q make_adder
Q3: 组合 (Compose)
编写函数 composed,它接受函数 f 和 g 并输出一个新函数。 这个新函数接受一个数字 x 并输出对 x 应用 g 之后再应用 f 的结果。
(define (composed f g)
'YOUR-CODE-HERE
)
使用 Ok 运行你的代码测试:
python3 ok -q composed
Q4: Repeat
编写一个名为 repeat 的过程, 它接受一个过程 f 和一个数字 n, 并返回一个新的过程。这个新过程接收一个数字 x, 并将 f 应用于 x,总共 n 次,并输出结果。例如:
scm> (define (square x) (* x x))
square
scm> ((repeat square 2) 5) ; (square (square 5))
625
scm> ((repeat square 3) 3) ; (square (square (square 3)))
6561
scm> ((repeat square 1) 7) ; (square 7)
49
提示: 你在前一个问题中编写的
composed函数可能很有用。
(define (repeat f n)
'YOUR-CODE-HERE
)
使用 Ok 运行你的代码测试:
python3 ok -q repeat
Q5: Greatest Common Divisor
GCD 指的是两个正整数的最大公约数。
编写过程 gcd,它使用欧几里得算法计算数字 a 和 b 的 GCD,该算法基于以下原理:两个数的最大公约数是以下情况之一:
- 如果较小的值能整除较大的值,那么最大公约数就是较小的值;或者
- 较小的值和较大值除以较小值的余数,这两者的最大公约数。
换句话说,如果 a 大于 b 并且 a 不能被 b 整除,则
gcd(a, b) = gcd(b, a % b)
你可能会发现提供的过程
min和max很有用。你也可以使用内置的modulo和zero?过程。scm> (modulo 10 4)
2
scm> (zero? (- 3 3))
#t
scm> (zero? 3)
#f
(define (max a b) (if (> a b) a b))
(define (min a b) (if (> a b) b a))
(define (gcd a b)
'YOUR-CODE-HERE
)
使用 Ok 运行你的代码测试:
python3 ok -q gcd
Check Your Score Locally
你可以通过运行以下命令,在本地查看本次作业中每个题目的得分情况
python3 ok --score
这不会提交作业! 当你对你的分数感到满意时,请将作业提交到 Gradescope 以获得学分。
Submit
请将你编辑过的所有文件上传至 Gradescope 上对应的作业页面,以提交本次作业。Lab 00 包含详细说明。
此外,所有没有参加大型实验课的学生都需要填写出勤表。每周提交此表格,无论你是否参加实验,或者因为正当理由而错过。参加大型实验课的学生无需填写出勤表。