Exercise 3: Mutual Recursion — isEven / isOdd — Possible Solution ==================================================================== THE PROGRAM ------------------------------ fun isEven(n) { if (n == 0) { return true; } return isOdd(n - 1); } fun isOdd(n) { if (n == 0) { return false; } return isEven(n - 1); } print isEven(10); print isOdd(10); RESULT ------------------------------ ['true', 'false'] WHY THIS WORKS, EVEN THOUGH isOdd DOESN'T EXIST YET WHEN isEven IS COMPILED ------------------------------ When the compiler processes `fun isEven(n) { ... return isOdd(n-1); }`, it compiles a reference to the GLOBAL name 'isOdd' -- specifically, visit_variable finds no local named 'isOdd' (resolve_local returns None), so it emits OP_GET_GLOBAL with 'isOdd' as a constant-pool string. Critically, this compiles successfully with NO check that a global named 'isOdd' actually exists anywhere yet -- Chapter 4's own design deliberately defers that check to RUNTIME, inside OP_GET_GLOBAL itself ("if name not in self.globals: raise..."), not to compile time. So compiling isEven's own body never asks "does isOdd exist right now?" -- it just emits an instruction that will ask that question later, when (and only when) the instruction actually executes. By the time isEven(10) is ACTUALLY CALLED (the third top-level statement), both `fun isEven` and `fun isOdd` have already run as statements, and both names are sitting in self.globals. The OP_GET_GLOBAL inside isEven's own body, when it finally executes, finds 'isOdd' present and correct. Tying this to a specific chapter 4 decision: this is the direct payoff of choosing OP_GET_GLOBAL/OP_SET_GLOBAL as NAME-KEYED, runtime-resolved lookups (a dict, checked when the instruction runs) rather than trying to resolve global references at compile time the way local slots are resolved. If Chapter 4 had instead tried to validate every global reference at compile time (the way it VALIDATES local slot numbers by tracking self.locals), compiling isEven's own body would have had to fail immediately, since isOdd genuinely doesn't exist as a compiler-visible fact yet at that point -- mutual recursion between top-level functions would have been structurally impossible without a two-pass compile step (declare every function's name first, THEN compile every body). Runtime resolution sidesteps that entire problem for free, as a direct consequence of a design choice made three chapters before functions existed at all.