A parse tree may need to support pretty printing, type checking, optimization, and code generation. Putting a virtual method for every operation on every node would mix those concerns into the tree hierarchy. The visitor pattern keeps each operation in a separate class, while each node provides a small dispatch method. section 9.4 section 9.4.1
Dispatch takes two steps:
- Define a visitor interface with one Visit overload per node type, such as Visit(Id) and Visit(Add). Its generic result type lets different visitors return different kinds of values. section 9.4.1
- Give the base node an abstract Accept method and implement it in each derived node as visitor.Visit(this). A call to Accept on a base-typed value reaches the derived implementation, where this has the specific node type needed to select the overload. section 9.4.1
abstract record class Expr
{
public abstract T Accept<T>(IExprVisitor<T> visitor);
}
sealed record class Id(string name) : Expr
{
public override T Accept<T>(IExprVisitor<T> visitor) =>
visitor.Visit(this);
}
Compare Expr.Accept with Id.Accept: the tree retains only the dispatch method; the operation-specific implementation belongs to the visitor. section 9.4.1
Pretty-printing an expression tree
An ExprToDoc visitor returns a Doc for each expression kind. An identifier becomes text; addition and multiplication share a binary-operator helper; calls assemble a parenthesized argument list. The resulting doc records grouping, indentation, and possible breaks for a later width-constrained formatting phase. section 9.4.2 section 9.5
The pattern does not eliminate changes to the tree: its node classes need Accept implementations. Once that dispatch machinery is present, further operations can live in new visitor classes rather than new virtual methods throughout the hierarchy. section 9.4.1 section 9.5



