Lesson 21 / 25
Iterator and Visitor in Brief
Walking collections and adding operations to structures.
Traversal without exposing internals
Iterator provides a way to access elements of a collection one by one without exposing its internal structure. It is now built into most languages: JavaScript's iteration protocol ([Symbol.iterator], for...of) and generator functions, Python's __iter__ and generators, and Java's Iterable. Generators also make lazy sequences easy, such as paging through an API. Visitor lets you add new operations to a fixed set of node types without changing those types: each node "accepts" a visitor, which has one method per node type. Compilers and linters use it to walk syntax trees (pretty-printing, type checking, evaluation). In TypeScript, a discriminated union with an exhaustive switch often replaces Visitor with less ceremony. Visitor is a poor fit when node types change often, since every visitor must be updated.
A paginated API as an iterator
An async generator hides the paging details.
async function* allInvoices(client: BillingClient): AsyncGenerator<Invoice> {
let cursor: string | undefined;
do {
const page = await client.listInvoices({ limit: 100, cursor });
yield* page.items;
cursor = page.nextCursor;
} while (cursor);
}
// Callers just loop; pages are fetched lazily as needed
for await (const invoice of allInvoices(client)) {
if (invoice.status === 'overdue') await sendReminder(invoice);
}
// Visitor-like operation via a discriminated union and exhaustive switch
type Expr =
| { kind: 'num'; value: number }
| { kind: 'add'; left: Expr; right: Expr }
| { kind: 'mul'; left: Expr; right: Expr };
function evaluate(e: Expr): number {
switch (e.kind) {
case 'num': return e.value;
case 'add': return evaluate(e.left) + evaluate(e.right);
case 'mul': return evaluate(e.left) * evaluate(e.right);
}
}A museum audio guide
An iterator is the guide that takes you from exhibit to exhibit without you knowing the building layout. A visitor is a specialist (a restorer, an insurer) touring the same exhibits and doing their own kind of work at each.
Quick check: When is Visitor a poor fit?
- When you need to add new operations to stable node types
- When new node types are added frequently, because every visitor must change
- When walking a syntax tree
- When building a linter
Answer
When new node types are added frequently, because every visitor must change — Visitor favours stable types and changing operations.