作者 | 梁唐
出品 | 公众号:Coder梁(ID:Coder_LT)
大家好,日拱一卒,我是梁唐。
我们继续来肝伯克利CS61A的scheme project,这是这个project的第三篇,如果漏掉了之前的建议先去补一下。
原始文档:https://inst.eecs.berkeley.edu/~cs61a/sp18/proj/scheme/#interpreter-details
在上一篇文章当中,我们进一步完善了scheme解释器的功能,让它能够支持自定义函数等许多功能。在这一篇文章当中,我们要继续完善功能,让我们的解释器能够支持更多语法。
Special Forms
整个项目到这里我们已经完成了基础的核心部分,接下来还要处理一些特殊的类型。
我们还有很多语法没有支持,比如逻辑运算符if
,and
, or
, cond
这些。这些表达式使用频率非常高,也非常重要。
在scheme当中,只有False
才是假,所有其他的值都会被视为真,包括0和nil
。你可以使用scheme_primitive.py
文件中的scheme_truep
和scheme_falsep
函数来判断一个值是true
还是false
。
注意:scheme传统中使用#f
来表示false
,#t
表示true
。在我们的解释器当中true
, True
, #t
均视为等价。然而在解锁测试数据时,需要使用#t
和#f
我们提供了if
语句的实现,do_if_form
函数作为参考。确保你已经理解了if
语句的原理,再进行下面的题目。
Problem 13
实现do_and_form
和do_or_form
函数来完成and
和or
表达式的支持。
对于and
语句来说,你需要从左往右evaluate所有的表达式,如果遇到结果是false
,那么返回#f
。否则返回最后一个子语句的结果。如果输入的语句为空,也返回#t
。
对于or
语句来说,我们也需要从左往右评估每一个子语句的值。如果某一个子语句的结果是true
,直接返回。否则返回#f
。如果输入为空,也返回#f
。
在开发之前,先答题解锁测试:
代码语言:javascript复制python3 ok -q 13 -u
开发之后,测试:
代码语言:javascript复制python3 ok -q 13
答案
这两个函数逻辑并不复杂,使用递归很容易搞定。
代码语言:javascript复制def do_and_form(expressions, env):
"""Evaluate a (short-circuited) and form."""
# BEGIN PROBLEM 13
"*** YOUR CODE HERE ***"
if expressions is nil:
return True
elif expressions.second is nil: # Tail context
return scheme_eval(expressions.first, env)
else:
first_expr = scheme_eval(expressions.first, env)
if scheme_falsep(first_expr): # The first expression is False
return False
elif scheme_truep(first_expr): # The first expression is True
return do_and_form(expressions.second, env)
# END PROBLEM 13
def do_or_form(expressions, env):
"""Evaluate a (short-circuited) or form."""
# BEGIN PROBLEM 13
"*** YOUR CODE HERE ***"
if expressions is nil:
return False
elif expressions.second is nil: # Tail context
return scheme_eval(expressions.first, env)
else:
first_expr = scheme_eval(expressions.first, env)
if scheme_falsep(first_expr): # The first expression is False
return do_or_form(expressions.second, env)
else: # The first expression is True
return first_expr
# END PROBLEM 13
Problem 14
完善do_cond_form
函数,让他能够返回第一个为true
的子语句的值,如果都为false
,返回else
语句的值。
然而存在一些特殊情况:
- 当判断为
true
的值没有对应的返回结果,那么返回该值 - 当
cond
语句的某一个分支中存在多个结果语句时,返回最后一个,提示,可以使用eval_all
函数
你的代码需要能通过下列测试数据:
如果cond
语句既没有为true
的分支也没有else
语句,那么返回None
编码之前,先回答问题解锁测试:
代码语言:javascript复制python3 ok -q 14 -u
编码之后,进行测试:
代码语言:javascript复制python3 ok -q 14
答案
整个函数的框架已经搭好了,函数内部使用了while
循环来遍历所有的表达式,并且实现了else
语句的部分。
test
表示当前的语句是否是true
,如果是真,那么返回test
之后的内容。但由于题目中说了,有些情况只有判断条件,没有返回结果。这个时候就要返回判断条件的结果本身,也就是test
。当然也有可能表达式有多个,这种情况使用eval_all
函数返回最后一个表达式的结果即可。
如果test
为假则继续往下执行其他条件。
def do_cond_form(expressions, env):
"""Evaluate a cond form."""
while expressions is not nil:
clause = expressions.first
check_form(clause, 1)
if clause.first == 'else':
test = True
if expressions.second != nil:
raise SchemeError('else must be last')
else:
test = scheme_eval(clause.first, env)
if scheme_truep(test):
# BEGIN PROBLEM 14
"*** YOUR CODE HERE ***"
if clause.second == nil:
return test
return eval_all(clause.second, env)
# END PROBLEM 14
expressions = expressions.second
Problem 15
let
特殊类型,它会在本地绑定symbol和value,但是这个绑定只会在原地生效,比如这个例子:
(let ((x 42) (y (* x 10))) (list x y))
语句当中,绑定y
时用到的x是全局的值,也就是5,而不是刚刚绑定的42。因为绑定值的操作是最后一起执行的,x
和y
是一起绑定的。有一点类似于以下的Python代码:
x = 5
x, y = 42, x * 10
实现make_let_frame
方法,它会返回一个当前env
的子frame
,在这个frame
当中,将symbol绑定到对应的value上。bindings
scheme list包含一系列pairs,其中每一个包含一个symbol和对应的表达式。
你可能会用到下面这些函数:
check_form
:这个函数用来检查binding
结构check_formals
:这个函数检查formal
参数,确保每一个symbol都是不同的make_child_frame
:这个函数(你在11题中开发的),它接收一个Pair
表示formal,一个Pair
表示value。返回一个绑定了formal和value的新frame
在你编码之前,先回答问题解锁测试样例:
代码语言:javascript复制python3 ok -q 15 -u
编码之后,进行测试:
代码语言:javascript复制python3 ok -q 15
答案
只要搞清楚了let
语句是先评估,评估完了之后一起绑定的,这题就算是搞定了一大半。
但坑爹的是,原文的题意当中没有明确地指出这一点,这是要我们基于老师给的问题和一些描述去自己分析和推理的。所以我当时做这题的时候,还是费了一些周折。
代码语言:javascript复制def make_let_frame(bindings, env):
"""Create a child frame of ENV that contains the definitions given in
BINDINGS. The Scheme list BINDINGS must have the form of a proper bindings
list in a let expression: each item must be a list containing a symbol
and a Scheme expression."""
if not scheme_listp(bindings):
raise SchemeError('bad bindings list in let form')
# BEGIN PROBLEM 15
"*** YOUR CODE HERE ***"
formals, args = nil, nil
while bindings is not nil:
check_form(bindings.first, 2, 2)
formals = Pair(bindings.first.first, formals)
args = Pair(scheme_eval(bindings.first.second.first, env), args)
bindings = bindings.second
check_formals(formals)
return env.make_child_frame(formals, args)
# END PROBLEM 15
Problem 16
实现do_mu_form
函数来评估特殊类型mu
,mu
不是一种标准的scheme表达式类型。
mu
表达式类似于lambda
表达式,但不同的是,当evaluate mu
语句时,它是动态scope的。lambda
表达式是静态scope,因为lambda
表达式执行的时候,使用的env
是lambda
创建时的frame
。而mu
使用的是表达式执行时的frame
,因此是动态的。
mu
表达式通过MuProcedure
类实现,大部分代码已经开发好了。
完善MuProcedure
类,当它被调用的时候,它依赖的外部frame
是动态的,也正因此,MuProcedure
类中不需要存储frame
实例。
下面是一个mu
表达式的例子:
编码之前,先回答问题确保理解正确:
代码语言:javascript复制python3 ok -q 16 -u
编码完成之后,进行测试:
代码语言:javascript复制python3 ok -q 16
答案
我们看下mu
表达式的使用方法,其实它和lambda
的语法是一样的。所以do_mu_form
和do_lambda_form
的逻辑也是一样的。
def do_mu_form(expressions, env):
"""Evaluate a mu form."""
check_form(expressions, 2)
formals = expressions.first
check_formals(formals)
# BEGIN PROBLEM 16
"*** YOUR CODE HERE ***"
return MuProcedure(formals, expressions.second)
# END PROBLEM 16
唯一的区别就是运行时的env
,lambda
是静态的,而mu
是动态的。
看起来好像很复杂,但仔细思考会发现这两个在实现里的区别其实很小。只有创建子frame
的时候有一些区别,lambda
表达式创建运行时的frame
用的是自身实例中存储的env
,这样就能保证不论什么时候运行lambda
,使用的都是创建时的frame
。
而mu
是动态的,从类的定义中我们也可以看出来,它没有env
这个实例属性。那么我们在创建子frame
的时候用的就不是本身存储的env
,而是外界传入的。外界传入的env
是在运行时传入的,这样就可以保证了,mu
运行时依赖的外部变量是动态的。
代码只有一行,但理清楚这里面的逻辑却不简单。
代码语言:javascript复制class MuProcedure(Procedure):
"""A procedure defined by a mu expression, which has dynamic scope.
_________________
< Scheme is cool! >
-----------------
^__^
(oo)_______
(__) )/
||----w |
|| ||
"""
def __init__(self, formals, body):
"""A procedure with formal parameter list FORMALS (a Scheme list) and
Scheme list BODY as its definition."""
self.formals = formals
self.body = body
# BEGIN PROBLEM 16
"*** YOUR CODE HERE ***"
def make_call_frame(self, args, env):
return env.make_child_frame(self.formals, args)
# END PROBLEM 16
def __str__(self):
return str(Pair('mu', Pair(self.formals, self.body)))
def __repr__(self):
return 'MuProcedure({0}, {1})'.format(
repr(self.formals), repr(self.body))
到这里,整个scheme的解释器就算是完成了。
但解释器除了要支持解释执行scheme代码之外,其实还有很多其他的内容。在附加题当中,老师给我们留了两个非常困难的问题:尾递归和尾递归的运行优化。
这部分内容比较困难,而且涉及到更多的知识,因此我们单独成篇,放在之后的文章当中和大家分享。
喜欢本文的话不要忘记三连~