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
Ast type will be a functor, so we can map over annotations and
will discard annotations.
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
Statement Void can never be constructed with a
WhileLoop can. So
a never ends up holding data but it lets us reuse the data type safely.