写在前面
本项目为UC Berekly CS61A SP24的 Project4: Scheme。以下为个人对此项目的见解,如有错误或更好的解决方案,欢迎与我联系。
具体思路
Problem 1
首先通读一下题目,此题需要我们完成两个函数define
和lookup
,同时给了我们关于Frame
的一些信息,Frame
有两个instance attribute。
bindings
这是一个字典,回顾一下字典的相关内容,字典,相当于其他语言的map,存在一个key和一个value,每个字典当中只能有一个唯一的key,但key中存放的数据可以是相同的。此题中,key就代表的是symbol,而value代表的就是其本意,相当于我们在python当中的等号赋值用法,例如,x = 3
在这里key就是x,value就是3。parent
每个frame都可以被嵌套在另一个frame里面,而他们最终的frame都是None。
define
非常显而易见了,上文中也提到过,在此不再赘述。
|
|
lookup
此函数可以想成三个case,题目当中也给了我们很详细的描述。
- 若symbol在当前frame里,直接返回value。
- 若symbol不在当前frame里,且其含有父frame,且父frame里含有此symbol,返回value。关于这个case,有几点需要说明
- 其含有父frame。问:如何界定此frame是否合法?答:只要不为None,都是合法的。
- 父frame里含有symbol。问:如何访问父frame?答:用点表达式访问parent即可。
- 若均不满足以上情况,即symbol不在当前frame里,且没有父frame,arise error。
|
|
Problem 2
首先通读一下题目,此题要求我们实现一个scheme_apply
函数中的一个case,即BuiltinProcedure
。在完成一个表达式时,存在内置的一些操作如 + - * /
等,在此,就需要我们实现这些操作。
BuiltinProcedure
含有两个instance attribute。
py_func
找到对应的操作符号,用python的内置函数来实现表达式,不用我们自己实现(自己似乎也实现不了,有点不切实际)。need_env
有些内置函数需要特定的环境。
此题可分为三个步骤来写,题目中也给的很清楚。
- 将
scheme list
转变为python list
,与上题很类似。 - 判断
need_env
的是否,很简单。 - 使用*args表示法来调用
py_func
这里可能需要了解一下什么是 *args表示法。
|
|
Problem 3
此题需要让我们将scheme_eval
补充完整,大致题目已给出,我们需要完成的是判断此表达式,并通过上文完成的scheme_apply
函数来计算结果。题目还是给了我们大致思路。
- 判断operator,使用递归。Recrusion,即自身调用自身,那一定是在
scheme_eval
函数里调用scheme_eval
。对于此函数,我们需要三个参数,expresson
environment
以及一个默认参数,文中给了我们代码提示first = expr.first
,由此即可以判断此处的expression = first
,同时还要求我们evaluate to a procedure instance。scheme_apply
的开头就给了我们提示 - 判断operands,依旧使用递归。与上文类似,题目已经给了我们
rest = expr.next
,同时题目要求我们收集到scheme列表里,下文有提示,使用Pair
的map
方法,观察map
方法,这提供了一个形参fn
,显然此处的fn
就是scheme_eval
,但scheme_eval
存在两个形参,这就需要我们进行转换,使用lambda函数。 - 之后就是调用
scheme_apply
,很显然,在此不做过多赘述。
|
|
Problem 4
此题要求我们实现define
,简单来说,就是将一个expressions赋值给一个symbol。我们在这里只需要实现第一个部分,即计算一个表达式作为expressions并复制给一个提供给我们的symbol,下方还有提示,需要我们用到Frame
里的define
。那我们想一想define
函数需要我们提供什么参数呢?
symbol
symbol总是存在于一个表达式的第一位,函数也给了我们一个提示,即signature = expressions.first
,由此我们就可以看出我们的symbol就是这个。expressions
对于一个表达式来说,我们需要赋值的不仅仅是表达式本身,还是它计算后的数值是什么,所以在这里我们还需要用到上文完成的scheme_eval
函数。- 最后,需要我们返回已绑定的
symbol
。
|
|
Problem 5
此题向我们描述了quote
的用法,在此不做过多赘述,详情可看题目要求。在此仅讲讲需要实现的功能。
给出一段expressions
,需要我们原封不动的返回,但对于Pair
来说,不返回nil,由此可见,需要返回的是expressions.first
。
|
|
Congratulation! 第一部分已完成
Problem 6
此题我们需要完成Scheme中的一个特殊形式begin
。
begin
用于按顺序组合多个表达式,并返回最后一个表达式的值。例如,
1 2 3 4
(begin (display "Hello") ; 执行第一个表达式(输出 "Hello") (display "World") ; 执行第二个表达式(输出 "World") (+ 1 2)) ; 返回最后一个表达式的值 3
那由此我们可以观察到,它会将所有表达式都执行一遍,但只返回最后一个表达式的值,是不是有点像我们说的递归的概念,不断的完成一个动作,直到最后一个动作再返回。由此我们就可以写出代码。
- 首先有一个
base case
若此表达式为空,返回None
。 - 开始递归。首先我们想一想,想要evaluate一个表达式,应该怎么做?答:用上文完成的
scheme_eval
函数。 - 如何判断是否到达了递归终点?对于一个
expressions
来说,它就像一个链表,最后一项的rest
总是nil
,这就到达了递归终点,也就是我们说的base case
。 - 对于
recrusion case
来说,我们要不断的调用自身,直到到达base case
。
|
|
Problem 7
此题依旧需要我们完成Scheme中的一个特殊形式lambda
。
lambda
用于定义匿名函数,此题要求我们逐步计算表达式,并返回最后一个表达式的值。
其中,LambdaProcedure
这个类存在一个实例body
,存放的是scheme list,而实例formals
存放的是正确的Pair嵌套表达式。
我们需要做的是创建并返回一个LambdaProcedure
实例,那整体思路就很明朗的,再加上此函数还有给我们的提示,使用特定的语法知识,易得。
|
|
Problem 8
此题需要我们创建一个新的Frame
,其中含有两个参数,formals
vals
,均为scheme list,需要我们将每一个formal对应到val,且formals于vals的数量都是相等的,一一对应,直到达到scheme list的终点即可。值得注意的是,不是简单的复制父Frame里的内容,笔者在这里第一次就想错了,总的来说,题目已经告诉我们具体做法,在此不做过多赘述。
|
|
Problem 9
此题需要我们完成scheme_apply
里的一个LambdaProcedure case
,题目已经给出提示,即
- 创建一个新Frame,注意到,要想创建一个新的Frame,就应该想到用谁来创建,不能是Procedure本身,因为
make_child_frame
是在Frame类中实现的,所以应该找到一个Frame,即Procedrure.env
- 将形参绑定到参数值,想一想,对于一个
Procedure
来说,存在两个实例,即formals
和body
,而formals
对应的就是make_child_frame
中的formals
,而body
对应的就是eval_all
中的expressions
。 - 在当前Frame进行计算,也就是说此时的
env
应该变成我们创建的新Frame
综上易得,
|
|
Problem 10
此题需要我们完成define
的一种新类型,题目已经给了详尽的步骤,在这里讲几个笔者踩到的坑。
- 想清楚
symbol
formals
body
分别代表什么,首先想明白,signature与expression之间的关系,signature代表的是expression的第一个内容,也就是函数名,而其中symbol
也叫函数,也就是signature.first
,之后再想想formals
,就是除了define和函数名剩下的内容,而body
则代表的是除了define之外的所有内容,想清楚这五个之间的关系,对于这道题十分关键。 - 题目告诉我们可以使用
do_lambda_form
,但笔者试了很多次也没有找到方法,若有人有更好的意见欢迎与我联系。 - 在找到正确的
formals
后还需要使用validate_formals()
函数来判断其正确性。
综上可知,
|
|
Problem 11
这道题看着题目很长很难,但其实是纸老虎,根据它的题意来即可。
- 看看
MuProcedure
可以发现需要两个参数,formals
和body
,formals
题目已经给出,指的是expressions的第一个内容,而body
就是除了第一个内容的其他内容。 - 完成
scheme_apply
的mu case
,这个内容跟上文的lambda case
如出一辙,有区别的是,Frame的区别,因为mu
是动态的,所以它的Frame一直在变,就不需要再用Procedure的Frame了。
综上可知,
|
|
|
|
Part 2 已完成!
Problem 12
这道题需要我们完成两个函数do_and_form
和do_or_form
,这两个函数从形式上来说都很相似,所以放在一起做,读题目可知,expressions会给出不定量个内容,我们需要判断每个内容的正确性来判断应返回什么,值得注意的是,对于and
和or
来说,存在一个关键的地方短路,每当遇到一个合适的内容时,就会舍弃后面的所有内容,仅返回当前内容。
因此,对于此题来说,递归是个很好的办法,具体步骤如下。
base case 1
若expression为空,则返回特定值。base case 2
每当我们判断出来的当前内容的正确性,则直接返回。base case 3
当所有内容都判断完毕后还没有找到正确性,则返回最后一个内容。recrusion case
不断对下一个内容执行当前函数。
代码如下,
|
|
|
|
Problem 13
此题需要我们完成cond
,题目也给了我们描述。返回第一个表达式为true
的值,若都不是正确的,那么就返回else
表达式也就是最后一个表达式的值,这也是一个递归步骤,因此可知步骤为,
base case
判断到最后一个表达式,若还没有判断出来,则返回此表达式的值。recrusion case
从第一个表达式开始逐步判断。
|
|
Problem 14
这道题确实没看懂什么意思,在此也不误人子弟了。
写在后面
后续的题目就不再做了,是关于Scheme的。
作为UCB的CS61系列的第一门课,在当时还是初学者的我留下了很深的心理阴影,Scheme这个项目也一直停着没有做,这次重新捡回来,也算是给自己的一个交代。
至此,CS61A结束。
YuTaki
2025年3月7日写于博学楼