“元循环评估者”(1998)

2020-07-13 23:25:26

我们将接受迟交的习题集,不加分(就像你在5月1日之前交上一样),直到5月8日(星期五)下午4点。

注:本作业中涉及的概念和问题实际上是保证会出现在第二次初稿和期末考试中的那类东西。确保你理解每个问题--现在不是过度依赖合作伙伴的时候。

在新的NOODLLE中,评估(加载ps6)。为了熟悉习题集,请尝试在解释器中计算(+12)。请阅读下面提供的特殊形式和原语的列表。

在本习题集中,您将修改一个用类似Dylan的语言计算表达式的程序。为了使评估器编写时使用的语言和评估器评估的语言保持不同,我们将其称为解释型语言布伦达(Brenda)。求值器重复从用户读入表达式,根据Brenda编程语言的规则对表达式求值,并打印出求值的表达式。我们称此循环为读取-求值-打印循环。以下是Brenda解释器中读取-求值-打印循环的Dylan代码:

(定义read-eval-print-loop(method()(bind((raw(Read))(result(brenda-eval raw*brenda-global-Environment*)(print";brenda?";raw)(print";brenda==>;";result)(read-eval-print-loop)。

在这个级别上,解释器非常容易理解,但是要更详细地理解它,我们需要了解两件事:brenda-eval泛型函数和*brenda-global-Environment*的结构。

brenda-eval泛型函数提供了元循环计算器的大部分功能。正如您从表达式求值的环境模型中回忆的那样,不同类型的对象以不同的方式求值。大多数对象都对自身求值,如<;String>;s、<;number>;s、<;Empty-list>;s和<;Boolean>;s,无论它们在什么环境中求值。当评估<;符号>;对象时,解释器会在评估它们的环境中查找它们的值。最后,通过计算组合的第一个元素并将函数应用于组合中的其余元素来计算<;对>;对象(组合或非空列表)。此功能由Brenda-Apply处理。以下是brenda.dyl中brenda-eval的代码。

(Define-Generic-Function Brenda-eval((obj<;object>;)(env<;brenda-frame>;);;大多数类型的对象都会自我求值。(add-method brenda-eval(method((obj<;object>;)(env<;brenda-frame>;)obj));;评估符号会导致查找其关联值。(Add-Method Brenda-eval(Method((obj<;Symbol&>)(env<;Brenda-Frame>;))(查找obj env);;评估一对调用brenda-Apply。(add-method brenda-eval(method((obj<;air>;)(env<;brenda-frame>;))(brenda-Apply(brenda-eval(Head Obj)env)(Ail Obj)env))

如您所见,brenda-eval的代码非常短。请注意,调用brenda-Apply时使用了一系列未计算的参数!(这是为了让我们可以正确地实现特殊表单。)。

无论何时评估组合,brenda-Apply泛型函数都会由brenda-eval调用。在Brenda中,就像在Dylan中一样,有三种对象可以位于组合的开头:用户定义的方法、特殊形式和原始方法。我们将用Dylan类在Brenda中表示这些不同类型的函数。以下是这些对象的类型定义:

;所有可以应用参数的对象都是函数(DEFINE-CLASS<;Brenda-Function>;(<;object>;));方法具有固定数量的类型化参数并计算它们的参数(DEFINE-CLASS<;Brenda-Method>;(<;Brenda-Function>;)(params<;list>;));;用户方法是使用方法特殊形式(DEFINE-CLASS<;brt;brt;)创建的方法。)(Body<;list>;)(env<;brenda-frame>;)。

;;基元是用户不能用布伦达表达的低级功能。(定义类<;brenda-primitive>;(<;brenda-method>;)(调用<;函数>;)。

;特殊表格(如DEFINE)不遵循正常评估规则。(定义类<;布伦达-特殊表单>;(<;布伦达-函数>;)(调用<;函数>;)。

请注意,<;brenda-primitive>;s和<;brenda-user-method>;都必须是<;brenda-method>;的实例,因为它们都计算其参数。作为<;brenda-method>;实例的对象有一个params槽,它是该方法具有的参数列表。用Dylan代码对原语方法进行了模拟。<;brenda-primitive>;的调用槽是一个可以模拟特定原语功能的Dylan函数。因为基元总是计算它们的参数,所以我们使用计算过的Brenda参数调用Dylan函数。a<;brenda-Special-form>;对象有一个类似于<;brenda-primitive>;的调用槽。此槽引用模拟特殊表单功能的Dylan函数。使用未计算的Brenda参数调用此Dylan函数。

a<;brenda-user-method>;是我们在环境模型中使用的闭包对象的Dylan实现。它们有三个插槽:参数、主体和环境。params只是该方法具有的参数列表。Body是应用方法时要计算的表达式列表。环境是定义方法的环境。下面将讨论操纵环境的方式。

Brenda-Apply Then是一个具有三种主要情况的泛型函数,分别对应于Brenda中的每种函数对象类型。以下是Brenda-Apply每种情况的代码:

(Define-Generic-Function Brenda-Apply((obj<;object&>)(args<;list&>)(env<;brenda-frame&>);;默认情况是错误(add-method brenda-Apply(method((obj<;object&>)(args<;list&>)(env<;Brenda-frame&>))(Brenda。OBJ);;原语;;评估原语所在环境中的参数;;调用。应用执行原语工作的Dylan函数;;仅在验证参数满足;;参数的类型约束之后。(add-method brenda-Apply(method((obj<;brenda-primitive>;)(args<;list>;)(env<;brenda-frame>;))(bind((Evaluated-args<;list>;)(map(method(X)(brenda-eval xenv))args)(args-params-Match?(Params Obj)Evaluated-args)(Apply(Call Obj)Evaluated-args);;特殊表单;;仅应用执行特殊表单工作的Dylan函数;;不计算参数。假定特殊表单将执行;;类型检查。(add-method brenda-Apply(method((obj<;brenda-Special-form>;)(args<;list>;)(env<;brenda-frame>;)((Call Obj)args env);;User Methods;;使用包含;;参数绑定的自变量的框架扩展定义方法的环境。在这个新的环境中评估身体。(Add-Method Brenda-Apply(Method((obj<;Brenda-user-method>;)(args<;list>;)(e<;brenda-frame>;))(bind(Evaluated-args<;list>;)(map(method(X)(brenda-eval xe))args)(args-params-Match?(Params Obj)Evaluated-args)(bind((new-env(Expand-Environment(Params Obj)Evaluated-args(Env Obj)(eval-equence(Body Obj)new-env)。

除了应用用户定义的方法之外,每个Brenda-Apply案例都非常简单。用户方法的应用分两个步骤执行。首先,使用包含绑定到参数的变量的新框架扩展定义该方法的环境。然后在这个新环境中对方法体进行评估。brenda-Apply代码利用了一些帮助器函数,如在brenda.dyl中定义的eval-equence、brenda-error和args-params-match?

在环境模型中,环境被定义为帧的有序列表。框架被定义为一组无序的绑定,而绑定是名称(符号)和值(对象)的关联。根据这些定义,我们可以使用classes.dyl中的以下代码提供相当自然的环境实现:

(定义类<;布伦达-帧&>(<;对象&>)(绑定<;列表&>))(定义类<;布伦达-本地-帧&>(<;布伦达-帧&>)(前<;布伦达-帧&>))(定义-类<;布伦达-全局-帧&>(<;布伦达-帧&>;)(变量<;符号>;)(值<;对象>;)。

虽然我们对帧的定义很简单,但是我们希望在环境结构上执行许多操作,例如查找绑定、查找名称和更改绑定的值。在文件frames.dyl中,有许多定义和帮助器函数提供此功能。以下是frames.dyl中为您提供的功能摘要:

在环境结构中查找具有给定名称的<;Brenda-binding>;对象。如果未找到,则返回#f。

在环境结构中查找与给定名称关联的值。如果未找到,则生成错误。

给定环境、参数列表和参数列表,将返回在给定环境之上构建的新环境,并将参数绑定到参数。

在解释器内部,我们必须有一种方法来强制方法、定义和绑定语句中的类型约束。不幸的是,解释器中的一些对象用其对应的Dylan对象表示,而另一些则使用Dylan结构更抽象地表示。例如,Brenda中的符号用Dylan<;符号>;对象表示。Brenda方法不是用Dylan<;方法>;对象表示的;它是用<;brenda-user-method>;实例表示的。

因为我们在解释器中有用不同种类的Dylan对象(一些本地数据、一些Dylan结构)表示的对象,所以我们将把每种类型的对象映射到一个关联的Brenda类对象。Brenda类对象将用Dylan结构表示,并且将有一个槽:超类,它将引用其超类:

我们将在Dylan中为Brenda中需要的每种类定义全局变量:

(定义Brenda-Object-class(make-Brenda-class();;Type<;object>;(定义Brenda-type-class(make-Brenda-class;;Type<;type>;(list Brenda-object-class)(定义Brenda-class-class(make-Brenda-class;;Type<;class>;(list Brenda-type-class))。

;;Number Class(定义Brenda-Number-class(make-Brenda-class;;Type<;Number>;(list Brenda-object-class)(定义Brenda-Integer-class(make-Brenda-class;;Type<;Integer>;(list Brenda-Number-class)(定义Brenda-Float-class(make-Brenda-class;;Type<;Float>;(list Brenda-Number-class))。

;方法类(定义Brenda-function-class(make-Brenda-class;;Type<;function>;(list Brenda-object-class)(定义Brenda-Method-class(make-Brenda-class;;Type<;method>;(list Brenda-function-class))。

;;List Class(定义Brenda-List-class(make-Brenda-class;;Type<;list>;(list Brenda-object-class)(定义Brenda-Pair-class(make-Brenda-class;;Type<;Pair>;(list Brenda-list-class)(定义Brenda-Empty-List-class(make-Brenda-class;;Type<;Empty-List>;(list Brenda-List-class))。

;;String class(定义Brenda-String-class(make-Brenda-class;;Type<;String>;(list Brenda-object-class)(定义Brenda-character-class(make-Brenda-class;;Type<;character>;(list Brenda-object-class))

然后,我们将有一个泛型函数brenda-get-class来告诉我们上面的哪些类应该与解释器使用的每种数据相关联。例如,brenda-function-class将是与<;brenda-Special-form>;关联的Brenda类,而brenda-symbol-class将是与<;符号>;关联的Brenda类。

(定义-泛型函数brenda-get-class(Obj))(add-method brenda-get-class(method((obj<;object>;)brenda-object-class)(add-method brenda-get-class(method((obj<;brenda-class&>))brenda-class-class))(add-method brenda-get-class(method(obj<;brenda-primitive>;))。Brenda-Special-Form&>))Brenda-function-class))(add-method brenda-get-class(method((obj<;brenda-user-method>;))brenda-method-class))(add-method brenda-get-class(method((obj<;boolean&>))brenda-boolean-class))(add-method brenda-get-class(method((obj<;boolean&>))(add-method brenda-get-class(method((obj<;boolean&>)(add-method brenda-get-class(method((obj<;boolean&>)。Pair>;))Brenda-Pair-class))(add-method brenda-get-class(method((obj<;Empty-list>;))brenda-Empty-list-class))(add-method brenda-get-class(method((obj<;Float&>))Brenda-Float-class))(add-method Brenda-get-class(method(obj<;Integer&>t;))Brenda-Integer。))brenda-string-class))(add-method brenda-get-class(method((obj<;Symbol>;))brenda-Symbol-class)。

现在,给定解释器中的任何对象,我们都可以找出它与哪个<;brenda-class>;相关联。在我们了解类型检查的工作原理之前,我们需要定义另一个称为subclass?的帮助器函数。子类?取两个<;Brenda-class>;ES,并确定第一个是否是第二个的子类。

(定义子类?;;B是A的子类吗?(method((a<;Brenda-class&>))(b<;Brenda-class&>))(if(id?a b)#t(orn(map(方法(A1)(子类?a1b))(超类a)。

现在,为了确定解释器中的对象o是否是某个<;brenda-class>;x的实例,我们找到对象o的类,并检查o的类是否是x的子类:

我们用布伦达-实例?在解释器中强制类型约束。我们不必强制使用单例类型,因为Brenda不支持它们。

有七个不同的源文件,您需要更改哪些源文件才能在Brenda中实现一些新功能,这可能会让人感到困惑。以下是每个文件中内容的快速摘要:

请确保您已经阅读了源代码!通常情况下,您只需编写一小段代码,这些代码以新的方式组合了我们提供给您的功能。如果您让解释器进入无法加载的状态,请查看启动期间打印的诊断消息,这样您就可以大致找出问题所在。如果这对您没有帮助,可以介绍您自己的brenda-debug-print行,这样您就可以改进问题所在。

虽然这很耗时,但每次在新的NOODLLE中进行重大更改时都要重新加载整个解释器。这将防止重复的类定义发生冲突,并将减少您通常遇到的问题。Brenda解释器加载可能会很慢,因此请尝试在可用的最快系统上工作。

最重要的是,在你潜心解决问题之前,想一想你将如何解决它。在这个问题集上比以往任何时候都做这件事会更好地服务于你。

在执行任何操作之前,请将堆栈限制更改为5000。除非你这么做,否则你连一个加分都过不了。

对于此问题,您将向解释器添加特殊形式的引号(';)。QUOTE应该接受一个参数,并简单地返回它而不计算。(提示:这几乎是现有的最容易定义的特殊形式。这应该不需要太多代码。)。

布伦达?(引用)布伦达=>;布伦达?(报价3)布伦达==>;3布伦达?(引号foo条)引号:仅接受1个参数。

对于此问题,您将向解释器添加特殊的表单定义。定义应该有两个参数:变量说明符和表达式。变量说明符的形式应为符号或(符号<;类型>;)。表达式应该在全局环境中求值,并且应该破坏性地修改全局环境以包含新的绑定。如果绑定存在,则应将其重置。如果存在可选类型,则应检查表达式的求值结果是否满足类型约束。如果没有,您应该发出错误信号。

布伦达?(定义x 12)布伦达==>;x布伦达?(定义(y<;整数>;)(+1(*x x)布伦达==>;y布伦达?(定义(z<;number>;)";hello";)参数不符合类型。布伦达?X布伦达==>;12布伦达?Y布伦达==>;145布伦达?(定义x)定义:参数太少。布伦达?(定义x 2 3)定义:参数太多。布伦达?(是否定义NULL?(method((x<;list>;))(=x(QUOTE()Brenda==>;NULL?布伦达?(空?(引用(1))布伦达==>;#f布伦达?(空?(引号())Brenda==>;#t。

对于此问题,您将向Brenda添加绑定方法特殊形式。bind-method应该接受两个或多个参数;第一个参数应该是方法绑定列表,其余参数应该是一系列一个或多个Brenda表达式。每个绑定的格式应为(名称参数正文)。名称应该是一个符号。您应该检查方法特殊形式的代码。使用提供的帮助器函数,您应该解析每个方法绑定,并从每个方法绑定构造<;brenda-user-method>;。然后将每个方法绑定到一个框架中,并将它们与它们的名称相关联。您的代码应该处理相互递归的函数,即相互调用的函数。所有方法都必须在同一框架中定义,以便支持相互递归的函数。

布伦达?(绑定方法((f(X)(+x2)(F 3))brenda==>;5 brenda?(bind-method((f x(+x 2)(F 3))bind-method:参数列表格式错误。布伦达?(bind-Methods((f(X)(if(=x0)1(g(-x1)(g(Y)(*2(F Y)(G3))brenda==>;16 brenda?(bind-Methods((g((y<;Integer>;))(*2(+1 y)(g 3.4))参数不符合类型。布伦达?(bind-Methods((f(X)39x6);提示:请确保将其与;;(39x6)区别对待--在这种情况下,39x6也很容易做到。(g(Y)(*2y)(+(G3)(F 42)Brenda==>;12。

在本问题中,您将在Brenda中实现STREAMS,其中STREAMS的行为与课程中介绍的完全相同。有很多方法可以做到这一点,但我们要选择的一种方法是在语言中添加一种特殊的形式。实现特殊形式的cons-stream、Head-Stream和Tail-Stream。这些函数的契约与cons、head和ail完全相同。然而,与常规的旧列表不同,流可以是无限长的。

要做这道题,你需要有一些延迟求值的方法。这类似于特殊形式的情况,例如,第二个或第三个参数在第一个参数之后求值。然而,在这种情况下,我们需要一种更通用的方法来实现这一点。延迟计算可以通过多种方式实现:我们可以使用宏,也可以使用称为thunks的概念。它们基本上是等价的,但是由于您以前见过宏,所以我们将使用thunk。

thunk是一个有四个槽的类--它存储稍后要求值的表达式,并存储表达式求值的环境、指示表达式是否已经求值的布尔标志以及值(如果已经求值)。

对于此问题,您需要执行以下操作:将新的特殊表单或函数cons-stream添加到Brenda。(提示:这不需要太多代码。)。

增列。

.