我正在研究使用 Haskell 构建编译器。我使用定点数据类型递归来表示抽象语法树(ast)。
我正在研究如何为具有简单表达式(数字和逻辑常量、二进制操作和局部变量声明)的玩具语言编写类型检查器。
类型检查器是一个读写状态 ( RWS
) monad:
- 阅读器,因为它使用由具有符号定义(符号及其类型的关联列表)的环境组成的上下文;
- writer,因为它会生成错误消息列表;
- 稍后将需要 state 来实现名义类型等价,现在我只是在计算程序中定义了多少变量(仅作为其使用的演示)。
monad 返回的值是一个用类型(用于表达式)或环境(用于声明)注释的 ast。
该函数checker
接收输入程序的 ast 并生成一个用RWS
monad 操作注释的新 ast,该操作在运行时给出类型(如果 ast 是表达式)或环境(如果 ast 是声明)。
例如,考虑输入程序
let x = 2 + 3 in 1 + x
与相应的 ast:
Let
|
-----------------------
| |
VarDec: x Bin Add
| |
| ------------
| | |
Bin Add Num 1.0 Var x
|
-----------
| |
Num 2.0 Num 3.0
类型检查它将产生以下 ast:
action1
Let
|
-----------------------
| |
action2 action3
VarDec: x Bin Add
| |
| ------------
| | |
action4 action5 action6
Bin Add Num 1.0 Var x
|
-----------
| |
action7 action8
Num 2.0 Num 3.0
RWS
这是用monad 动作递归注释的。
编译器的后期阶段将需要知道 ast 中的注释产生的信息(及其子级,递归地)。因此需要运行相应的操作来获取它。
根据语言规则,通过组合孩子的动作来构造根动作。
例如,为了获得根表达式的类型(一个 let 表达式),action1
必须运行,这将使得action2
andaction3
也被运行,因为当action1
它被创建时,它使用了action2
and action3
。
当1+x
需要添加的类型时,action3
必须运行才能获得它。
所以动作会重复运行。类型检查器的结构方式(使用RWS
monad 操作)失去了对 ast 的每个节点的计算信息的共享。
是否有任何技术可以恢复这种共享,从而消除重新计算操作的需要?