You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
既然我们已经描述了 Logo 解释器的结构,我们转而实现`Environment `类,便于让它使用动态作用域正确支持赋值、过程定义和变量查找。`Environment`实例表示名称绑定的共有集合,可以在程序执行期间的某一点上访问。绑定在帧中组织,而帧以 Python 字典实现。帧包含变量的名称绑定,但不包含过程。运算符名称和`Procedure`实例之间的绑定在 Logo 中是单独储存的。在这个实现项目中,包含变量名称绑定的帧储存为字典的列表,位于`Environment`的`_frames`属性中,而过程名称绑定储存在值为字典的`procedures`属性中。
`lookup_variable `方法在求解变量名称时由`logo_eval`调用。`set_variable_value `由`logo_make`函数调用,它用作 Logo 中`make`基本过程的主体。
946
+
947
+
除了变量和`make`基本过程之外,我们的解释器支持它的第一种抽象手段:将名称绑定到值上。在 Logo 中,我们现在可以重复第一章中的,我们的第一种抽象步骤。
948
+
949
+
```logo
950
+
? make "radius 10
951
+
? print 2 * :radius
952
+
20
953
+
```
954
+
955
+
赋值只是抽象一种有限总是。我们已经从这门课的开始看到,即使对于不是很大的程序,用户定义函数是个管理复杂性的关键工具。我们需要两个改进来实现 Logo 中的用户定义过程。首先,我们必须描述`eval_definition`的实现,如果当前行是定义,`logo_eval`会调用这个 Python 函数。其次,我们需要在`logo_apply`中完成我们的表描述,它在一些参数上调用用户过程。这两个改动都需要利用上一节定义的`Procedure`类。
956
+
957
+
定义通过创建新的`Procedure`实例来求值,它表示用户定义的过程。考虑下面的 Logo 过程定义:
定义的第一行提供了过程的名称`factorial`和形参`n`。随后的一些行组成了过程体。这一行并不会立即求值,而是为将来使用而储存。也就是说,这些行由`eval_definition`读取并解析,但是并不传递给`eval_line`。主体中的行一直读取,直到出现了只包含`end`的行。在 Logo 中,`end`并不是需要求值的过程,也不是过程体的一部分。它是个函数定义末尾的语法标记。
968
+
969
+
`Procedure`实例从这个过程名称、形参列表以及主体中创建,并且在环境中的`procedures`的字典属性中注册。不像 Python,在 Logo 中,一旦过程绑定到一个名称,其它定义都不能复用这个名称。
970
+
971
+
`logo_apply`将`Procedure`实例应用于一些参数,它是表示为字符串的 Logo 值(对于单词),或列表(对于句子)。对于用户定义过程,`logo_apply`创建了新的帧,它是一个字典对象,键是过程的形参,值是实参。在动态作用域语言例如 Logo 中,这个新的帧总是扩展自过程调用处的当前环境。所以,我们将新创建的帧附加到当前环境上。之后,主题中的每一行都依次传递给`eval_line `。最后,在主体求值完成后,我们可以从环境中移除新创建的帧。由于 Logo 并不支持高阶或一等过程,在程序执行期间,我们并不需要一次跟踪多于一个环境。
972
+
973
+
下面的例子演示了真的列表和动态作用域规则,它们由调用这个两个 Logo 的用户定义过程产生:
974
+
975
+
```logo
976
+
? to f :x
977
+
> make "z sum :x :y
978
+
> end
979
+
? to g :x :y
980
+
> f sum :x :x
981
+
> end
982
+
? g 3 7
983
+
? print :z
984
+
13
985
+
```
986
+
987
+
从这些表达式求值中创建的环境分为过程和帧,它们维护在分离的命名空间中。帧的顺序由调用顺序决定。
988
+
989
+

990
+
991
+
## 3.6.5 数据即程序
992
+
993
+
在思考求值 Logo 表达式的程序时,一个类比可能很有帮助。程序含义的一个可取观点是,程序是抽象机器的描述。例如,再次思考下面的计算阶乘的过程:
与之相似,我们可以将 Logo 解释器看做非常特殊的机器,它接受机器的描述作为输入。给定这个输入,解释器就能配置自己来模拟做描述的机器。例如,如果我们像解释器中输入阶乘的定义,解释器就可以计算阶乘。
1013
+
1014
+

1015
+
1016
+
从这个观点得出,我们的 Logo 解释器可以看做通用的机器。当输入以 Logo 程序描述时,它模拟了其它机器。它在由我们的编程语言操作的数据对象,和编程语言自身之间起到衔接作用。想象一下,一个用户在我们正在运行的 Logo 表达式中输入了 Logo 表达式。从用户的角度来看,类似`sum 2 2`的输入表达式是编程语言中的表达式,解释器应该对其求值。但是,从解释器的角度来看,表达式只是单词组成的句子,可以根据定义好的一系列规则来操作它。
1017
+
1018
+
用户的程序是解释器的数据,这不应该是混乱的原因。实际上,有时候忽略这个差异会更方便,以及让用户能够显式将数据对象求值为表达式。在 Logo 中,无论我们何时使用`run `过程,我们都使用了这种能力。Python 中也存在相似的函数:`eval`函数会求出 Python 表达式,`exec`函数会求出 Python 语句,所以:
1019
+
1020
+
```py
1021
+
>>>eval('2+2')
1022
+
4
1023
+
```
1024
+
1025
+
和
1026
+
1027
+
```py
1028
+
>>>2+2
1029
+
4
1030
+
```
1031
+
1032
+
返回了相同的结果。求解构造为指令的一部分的表达式是动态编程语言的常见和强大的特性。这个特性在 Logo 中十分普遍,很少语言是这样,但是在程序执行期间构造和求解表达式的能力,对任何程序员来说都是有价值的工具。
0 commit comments