讨论 10 | CS 61A 2024 春季
讨论 10:解释器
提醒: 我们仍然会使用 Pensieve,但是我们已经从 Pensieve 中移除了语音/视频聊天功能。请使用 Discord 与课程工作人员进行语音聊天。它更可靠,并且包含屏幕共享功能。随时在 Discord 的 #discuss-queue 频道中向 @discuss 发送消息,课程工作人员会加入您小组的语音频道。
请小组里选一个人加入 Discord。 也可以多个人加入,但一个人就够了。
现在请切换到 Pensieve:
- 所有人:前往 discuss.pensieve.co 并使用您的 @berkeley.edu 电子邮件登录,然后输入您的小组号码。(您的小组号码是您的 Discord 频道号码。)
进入 Pensieve 后,你不需要返回此页面;Pensieve 包含了所有相同的内容(而且功能更多)。如果 Pensieve 因为某些原因无法使用,请返回此页面继续讨论。
如果您遇到问题,请在 Discord 上的 #help 频道中发帖。
小贴士: 你们可以在小组的 Discord 频道文本聊天里,用 @discuss 标签提问,课程工作人员会回复。
开始之前
如果您的团队只有 1 或 2 个人,您可以加入房间里的其他团队。
大家轮流说一下自己的名字,然后看看谁最近有离开湾区旅行的计划。
表示列表
Scheme 调用表达式是一个 Scheme 列表,它在 Python 中使用 Pair 实例表示。
例如,调用表达式 (+ (* 3 4) 5) 表示为:
Pair('+', Pair(Pair('*', Pair(3, Pair(4, nil))), Pair(5, nil)))
Pair 类和 nil 对象在 Scheme 项目 的 pair.py 中定义。
class Pair:
"A Scheme list is a Pair in which rest is a Pair or nil."
def __init__(self, first, rest):
self.first = first
self.rest = rest
... # There are also __str__, __repr__, and map methods, omitted here.
Q1:表示表达式
请写出以下每个 Pair 所代表的 Scheme 表达式,并使用 Scheme 语法。 也可以尝试画出链表图。 第一个例子已经完成了。
Pair('+', Pair(Pair('*', Pair(3, Pair(4, nil))), Pair(5, nil)))
答案:(+ (* 3 4) 5)
>>> Pair('+', Pair(1, Pair(Pair('*', Pair(2, Pair(3, nil))), nil)))
你的答案:
(+ 1 (* 2 3))
>>> Pair('and', Pair(Pair('<', Pair(1, Pair(0, nil))), Pair(Pair('/', Pair(1, Pair(0, nil))), nil)))
你的答案:
(and (< 1 0) (/ 1 0))
讨论环节: (and (< 1 0) (/ 1 0)) 的计算结果是什么? 在您的小组中讨论直到你们都同意,然后在您小组的 Discord 频道文本聊天里发布您的答案。
表达式求值
为了用 Project 4 的解释器求值表达式 (+ (* 3 4) 5),scheme_eval 函数会按以下顺序被调用,参数是以下表达式:
(+ (* 3 4) 5)+(* 3 4)*345
到了讨论时间:互相讨论一下,为什么 * 会被求值/评估,结果是什么。
* 被求值是因为它是 (* 3 4) 这个表达式中的运算符,而 (* 3 4) 又是 (+ (* 3 4) 5) 这个表达式中的操作数。
默认情况下,* 会被求值为一个函数,这个函数的功能是将它的参数相乘。但是,* 随时可能被重新定义,所以每次用到符号 * 的时候,都必须重新求值,才能找到它当前的值。
scm> (* 2 3) ; 现在是乘法
6
scm> (define * +)
*
scm> (* 2 3) ; 现在是加法
5
if 表达式也是 Scheme 中的一种列表,它通过 Pair 实例来表示。
例如,(if (< x 0) (- x) x) 表示为:
Pair('if', Pair(Pair('<', Pair('x', Pair(0, nil))), Pair(Pair('-', Pair('x', nil)), Pair('x', nil))))
如果 x 被绑定为 2,且 < 和 - 都是默认值,那么为了求值这个表达式,scheme_eval 函数会按以下顺序被调用,参数是以下表达式:
(if (< x 0) (- x) x)(< x 0)<x0x
到了展示时间:请想出一个简单的解释,说明为什么 if 和 - 没有被求值,即使它们都出现在 (if (< x 0) (- x) x) 这个表达式里。 一旦你们小组对答案达成一致,或者需要帮助,就在 #discuss-queue 频道里,用 @discuss 标签加上你们的小组编号,再发送消息 "If you please!"。 课程工作人员会加入你们的语音频道,听取你们的解释并给出反馈。
符号 if 没有被求值,因为它是一个特殊形式的开头,而不是函数调用表达式的一部分。 引入特殊形式的符号(and、if、lambda 等)永远不会被求值。
符号 - 没有被求值,包含它的子表达式 (- x) 也没有被求值,因为 (< x 0) 的求值结果是 #f。 如果你们还是不确定哪些部分会被求值,哪些不会,请咨询课程的助教。
Q2:评估
如果 x 被绑定为 -2,那么在对表达式 (if (< x 0) (- x) (if (= x -2) 100 y)) 调用 scheme_eval 函数时,以下哪些会被求值? (假设 <、- 和 = 具有其默认值。)
if<=xy0-2100-()
当 x 被绑定为 -2 时,(< x 0) 的求值结果是 #t,所以 (- x) 会被求值,但 (if (= x -2) 100 y) 不会被求值。 对于每一个需要求值的函数调用表达式,它的运算符和操作数都会被求值。 (< x 0) 和 (- x) 都是函数调用表达式。
请在你们小组的 Discord 频道文本聊天里,列出你们的答案。 例如,如果您认为这四个被评估,请写 if < 0 )。 课程工作人员将审查你们的答案并提供反馈。
Q3:打印评估的表达式
定义一个名为 print_evals 的函数,它接受一个 Scheme 表达式 expr 作为参数,该表达式仅包含数字、加号 +、乘号 * 和括号。它会按照表达式传递给 scheme_eval 的顺序,打印求值过程中所有被求值的表达式。
提示: 对 Pair 实例使用 print 函数会打印出它所代表的 Scheme 表达式。
>>> print(Pair('+', Pair(Pair('*', Pair(3, Pair(4, nil))), Pair(5, nil))))
(+ (* 3 4) 5)
你的答案
在 61A 代码中运行
解决方案
def print_evals(expr):
"""Print the expressions that are evaluated while evaluating expr.
expr: a Scheme expression containing only (, ), +, *, and numbers.
>>> nested_expr = Pair('+', Pair(Pair('*', Pair(3, Pair(4, nil))), Pair(5, nil)))
>>> print_evals(nested_expr)
(+ (* 3 4) 5)
+
(* 3 4)
*
3
4
5
>>> print_evals(Pair('*', Pair(6, Pair(7, Pair(nested_expr, Pair(8, nil))))))
(* 6 7 (+ (* 3 4) 5) 8)
*
6
7
(+ (* 3 4) 5)
+
(* 3 4)
*
3
4
5
8
"""
if not isinstance(expr, Pair):
print(expr)
else:
print(expr)
while expr is not nil:
print_evals(expr.first)
expr = expr.rest
如果 expr 不是一个 pair,那么它就是一个数字、'+' 或 '*'。 在这些情况下,打印 expr 是为了表明它会被求值。
如果 expr 是一个 pair,则它是一个调用表达式。 打印该表达式后,操作符和操作数会被求值。 这些元素都包含在 expr 列表中,因此可以使用 while 语句或 expr.map(...) 遍历 expr,并在每个元素上调用 print_evals 函数。
填写出勤表
温馨提示: 请在离开前帮忙把房间里的家具放回原位。 谢谢!