When writing a compiler, one typically annotates the syntax tree in various phases. What the nodes are annotated with will vary over the course of the program; one might add type information during a type synthesis phase.
By making the abstract syntax tree polymorphic in the annotation type, we get type safety at all phases. For example:
data Type = IntTy
| ...
data Ast a = IntLit a Int
| StringLit a String
| ...
annotateTypes :: Ast () -> Ast Type
This Ast
type will be a functor, so we can map over annotations and void
will discard annotations.
Syntactic Sugar
We can use polymorphism to ban certain constructors at certain phases. This could be useful if you have a desugaring phase which (for instance) rewrites all for-loops into while-loops.
We could share a data type between parsing and desugaring like so:
data Statement a = ForLoop a ...
| WhileLoop ...
parse :: String -> [Statement a]
desugar :: Statement () -> Statement Void
A Statement Void
can never be constructed with a ForLoop
but
a WhileLoop
can. So a
never ends up holding data but it lets us reuse the data type safely.