Exercise 1: A Ternary (Conditional) Expression Node — Possible Solution ==================================================================== THE NEW NODE TYPE ------------------------------ @dataclass class Ternary(Expr): cond: Expr then_branch: Expr else_branch: Expr def accept(self, visitor): return visitor.visit_ternary(self) Same shape as every other node class in the chapter: one dataclass, one accept() method that calls back into whichever visitor is walking the tree. Nothing about adding a new node type is special-cased. THE TWO VISIT METHODS ------------------------------ class Evaluator: ... def visit_ternary(self, node): return node.then_branch.accept(self) if node.cond.accept(self) else node.else_branch.accept(self) class AstPrinter: ... def visit_ternary(self, node): return f"(ternary {node.cond.accept(self)} {node.then_branch.accept(self)} {node.else_branch.accept(self)})" RESULTS ------------------------------ Tree for '1 ? 2 : 3': Ternary(Literal(1.0), Literal(2.0), Literal(3.0)) tree.accept(Evaluator()) -> 2.0 tree.accept(AstPrinter()) -> (ternary 1.0 2.0 3.0) WHY THIS WORKS AS AN ANSWER ------------------------------ This is exactly the "adding an operation is cheap, adding a node type is not" tradeoff the chapter names directly. Because Ternary is a NEW node type, both existing visitors (Evaluator and AstPrinter) genuinely needed a new method each — that cost is real and the chapter doesn't pretend otherwise. What the chapter's own dispatch-count finding predicts, and what this exercise confirms, is that neither existing visitor needed its OTHER methods touched at all: visit_literal, visit_binary, visit_unary, and visit_grouping are exactly as they were before Ternary existed. The isinstance-chain equivalent would have required inserting a new elif branch into the middle of every existing function's own type-check chain instead of appending one clean new method to the end of each class.