You started with a single rule:
"n": (n) => n
A number evaluates to itself. That was the whole language.
From there you added operators, then variables, then functions. You hit a bug where variables clobbered each other and fixed it by giving each function its own environment. That fix had a name: closures.
You used closures to build data. A pair is just a function that remembers two values and lets the caller choose. A list is a chain of pairs ending in nil, a type you invented because you needed a way to say "nothing here."
You wrote syntactic sugar: twelve one-line functions that changed nothing about the language but made it readable. And in the final problem, you built an interpreter inside your interpreter, then watched the same expression produce a number, a string, and a count, three meanings from one tree.
Along the way, a pattern kept appearing: tagged data plus dispatch. Your evaluator checks a tag (number? string? array with "if"?) and calls the matching rule. Your pairs use a selector to choose between two values. Your expression trees use a tag to choose between lit, add, and mul. It is the same idea every time. Data carries a label. Code reads the label and decides what to do.
This pattern has many names in other contexts: pattern matching, dynamic dispatch, polymorphism, the visitor pattern. You have been using it since step one.
What you built is small. It fits in a page. But the ideas in it are the same ideas that run real languages. Lexical scoping, closures, environments, tagged data, recursive evaluation: these are not simplifications. They are the actual mechanisms.
The difference between your language and a production one is not in the ideas. It is in the engineering: parsing, optimization, error messages, tooling, and thousands of edge cases. The foundation is what you just built.
Further reading
- 1.1.3 Evaluating Operator Combinations — How nested expressions get evaluated, the tree walk your evaluator does
- 1.2.1 Linear Recursion and Iteration — The difference between recursive and iterative processes you explored
- 4.1.1 The Core of the Evaluator — The evaluate/apply loop you implemented
- 3.2 The Environment Model of Evaluation — The scoping bug you hit and how environments fix it
- 2.1.1 Pairs — The same pair-as-a-function trick you used to build data
- 2.4 Multiple Representations for Abstract Data — Tagged data and dispatch, the pattern that kept appearing
- 4.1.2 Representing Expressions — How a real evaluator handles syntactic sugar