I am studying compiler construction using Haskell. I am using fixed point data type recursion to represent abstract syntax trees (ast).
I am investigating how to write the type checker for a toy language having simple expressions (numeric and logic constants, binary operations and local variable declarations).
The type checker is a read-write-state (RWS
) monad:
The value returned by the monad is an ast annotated with types (for expressions) or environments (for declarations).
The function checker
receives an ast of the input program and results in a new ast annotated with RWS
monad actions that, when run, gives the type (if the ast is an expression) or the environment (if the ast is a declaration).
For instance, consider the input program
let x = 2 + 3 in 1 + x
with the corresponding ast:
Let
|
-----------------------
| |
VarDec: x Bin Add
| |
| ------------
| | |
Bin Add Num 1.0 Var x
|
-----------
| |
Num 2.0 Num 3.0
Type checking it will produce the following 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
which is recursively annotated with RWS
monad actions.
Later phases of the compiler will need to know the information produced by the annotations in the ast (and its children, recursively). Therefore it will be needed to run the corresponding action to get it.
A root action is constructed by composing the actions of the children, according to the rules of the language.
For instance, in order to get the type of the root expression (a let expression), action1
has to be run, and that will make action2
and action3
to be run as well, because when action1
was created, it used action2
and action3
.
When the type of the addition 1+x
is needed, action3
has to be run in order to get it.
So actions will be run repeatedly. The way the type checker is structured (using RWS
monad actions) loses sharing of the computed information for each node of the ast.
Is there any technique to recover this sharing, eliminating the need of recomputing actions?
It sounds like your design has turned into a blind alley. You are showing us a bit of how it looks.
I am studying compiler construction using Haskell.
Studying implies you could read how other people do type checking (e.g. GHC or an example from Oleg). Or it may be your mean to learn more by trying invent.
The way the type checker is structured...loses sharing of the computed information for each node of the ast.
So don't lose the information. If the type checker is run inside a monad then you can, eventually, design it to remember the state.
Is there any technique to recover this sharing, eliminating the need of recomputing actions?
More concretely, you want to replace the actionX with its results after it has been run. This looks very very very much like a desire for a lazy value: compute once and memorize the result (only slightly tricky with errors).
Perhaps each action recalculates the subtree from its node, including itself. The children's actions are replaced by their results (and recalculated subtrees).
Or if your AST is one immutable thing then perhaps the state should be a parallel AST.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With