From harness-claude
Implements GOF Visitor pattern in TypeScript to add operations like evaluation, printing, counting to object structures such as ASTs without modifying classes. Use for tree traversals and double dispatch.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Add operations to object structures without modifying them using double dispatch.
Implements JavaScript Visitor pattern to add operations to complex object structures like ASTs, file trees, or DOM without modifying element classes. Use for many unrelated operations on stable hierarchies.
Generates Visitor pattern for PHP 8.4 to perform operations on object structures without modifying element classes. Includes visitor interface, concrete visitors, visitable elements, and unit tests.
Mandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
Share bugs, ideas, or general feedback.
Add operations to object structures without modifying them using double dispatch.
Double dispatch visitor:
// Element interface — accept any visitor
interface Expression {
accept<T>(visitor: ExpressionVisitor<T>): T;
}
// Concrete elements
class NumberLiteral implements Expression {
constructor(public readonly value: number) {}
accept<T>(visitor: ExpressionVisitor<T>): T {
return visitor.visitNumber(this);
}
}
class AddExpression implements Expression {
constructor(
public readonly left: Expression,
public readonly right: Expression
) {}
accept<T>(visitor: ExpressionVisitor<T>): T {
return visitor.visitAdd(this);
}
}
class MultiplyExpression implements Expression {
constructor(
public readonly left: Expression,
public readonly right: Expression
) {}
accept<T>(visitor: ExpressionVisitor<T>): T {
return visitor.visitMultiply(this);
}
}
// Visitor interface — one method per element type
interface ExpressionVisitor<T> {
visitNumber(node: NumberLiteral): T;
visitAdd(node: AddExpression): T;
visitMultiply(node: MultiplyExpression): T;
}
// Concrete visitors — separate operations from the object structure
// Evaluate the expression
class EvaluateVisitor implements ExpressionVisitor<number> {
visitNumber(node: NumberLiteral): number {
return node.value;
}
visitAdd(node: AddExpression): number {
return node.left.accept(this) + node.right.accept(this);
}
visitMultiply(node: MultiplyExpression): number {
return node.left.accept(this) * node.right.accept(this);
}
}
// Pretty print the expression
class PrintVisitor implements ExpressionVisitor<string> {
visitNumber(node: NumberLiteral): string {
return `${node.value}`;
}
visitAdd(node: AddExpression): string {
return `(${node.left.accept(this)} + ${node.right.accept(this)})`;
}
visitMultiply(node: MultiplyExpression): string {
return `(${node.left.accept(this)} * ${node.right.accept(this)})`;
}
}
// Count nodes
class CountVisitor implements ExpressionVisitor<number> {
visitNumber(_: NumberLiteral): number {
return 1;
}
visitAdd(node: AddExpression): number {
return 1 + node.left.accept(this) + node.right.accept(this);
}
visitMultiply(node: MultiplyExpression): number {
return 1 + node.left.accept(this) + node.right.accept(this);
}
}
// Build and visit the AST: (2 + 3) * 4
const ast = new MultiplyExpression(
new AddExpression(new NumberLiteral(2), new NumberLiteral(3)),
new NumberLiteral(4)
);
const evaluator = new EvaluateVisitor();
const printer = new PrintVisitor();
const counter = new CountVisitor();
console.log(ast.accept(evaluator)); // 20
console.log(ast.accept(printer)); // ((2 + 3) * 4)
console.log(ast.accept(counter)); // 5
Discriminated union alternative (TypeScript idiomatic — often cleaner):
type Expr =
| { kind: 'number'; value: number }
| { kind: 'add'; left: Expr; right: Expr }
| { kind: 'multiply'; left: Expr; right: Expr };
function evaluate(expr: Expr): number {
switch (expr.kind) {
case 'number':
return expr.value;
case 'add':
return evaluate(expr.left) + evaluate(expr.right);
case 'multiply':
return evaluate(expr.left) * evaluate(expr.right);
// TypeScript errors if a case is missing — exhaustiveness checking
}
}
function print(expr: Expr): string {
switch (expr.kind) {
case 'number':
return `${expr.value}`;
case 'add':
return `(${print(expr.left)} + ${print(expr.right)})`;
case 'multiply':
return `(${print(expr.left)} * ${print(expr.right)})`;
}
}
Stateful visitor (accumulating results):
class FileSystemVisitor {
private totalSize = 0;
private fileCount = 0;
private maxDepth = 0;
visitFile(file: File, depth: number): void {
this.totalSize += file.size;
this.fileCount++;
this.maxDepth = Math.max(this.maxDepth, depth);
}
visitDirectory(dir: Directory, depth: number): void {
// No accumulation needed — just recurse
for (const child of dir.children) {
if (child instanceof File) this.visitFile(child, depth + 1);
else if (child instanceof Directory) this.visitDirectory(child, depth + 1);
}
}
getReport(): { totalSize: number; fileCount: number; maxDepth: number } {
return { totalSize: this.totalSize, fileCount: this.fileCount, maxDepth: this.maxDepth };
}
}
When discriminated unions beat Visitor: If you own both the element types and the operations (no third-party hierarchy to extend), prefer discriminated unions — they're simpler and TypeScript's exhaustiveness checking prevents missing cases. Use Visitor when you need to add operations to a class hierarchy you don't own.
Adding a new element type forces all visitors to update — this is the tradeoff. Adding new operations (new Visitor) is free. Adding new types breaks all existing Visitors. This is the "expression problem."
Anti-patterns:
accept() — breaks double dispatchDouble dispatch explained: When you call node.accept(visitor), the element type is resolved (first dispatch). The accept method then calls visitor.visitNumber(this) — the visitor type is resolved (second dispatch). This gives you the right behavior for each element-visitor combination without instanceof checks.
refactoring.guru/design-patterns/visitor