写在前面
本项目为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。对于此函数,我们需要三个参数,expressonenvironment以及一个默认参数,文中给了我们代码提示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函数需要我们提供什么参数呢?
symbolsymbol总是存在于一个表达式的第一位,函数也给了我们一个提示,即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的一种新类型,题目已经给了详尽的步骤,在这里讲几个笔者踩到的坑。
- 想清楚
symbolformalsbody分别代表什么,首先想明白,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日写于博学楼
