Design Philosophy: Simplicity as a Feature¶
The SICP Paradox¶
SICP teaches us to build powerful systems from simple primitives. Yet their pattern matching system (Chapter 4.4) is quite complex, featuring:
- Full unification with bidirectional matching
- Skeleton extraction and instantiation
- Rule ordering by specificity
- Closed pattern language
We chose a different path.
Our Design Decision: Computational Weakness as Strength¶
Our pattern matcher is deliberately less powerful than SICP's. This is the key design decision that shapes everything else.
What We Don't Have¶
- No Unification:
when('f', '$x', '$x')matchesf(a,b)withx=aandx=b, not enforcing equality - No Bidirectional Matching: Patterns match trees, not vice versa
- No Rule Ordering: First match wins, period
- No Pattern Variables in Results: Can't write
then('$x')to reuse bindings
What We Gain¶
1. Transparent Simplicity¶
def rewrite(tree, *rules):
"""The entire rewriting engine."""
while True:
for rule in rules:
new = rule(tree)
if new != tree:
tree = new
break
else:
return tree
Ten lines. No magic. A CS freshman can understand it completely.
2. Python as First-Class Pattern Language¶
SICP's patterns are a closed language. Ours are just Python:
# SICP style (hypothetical Python port):
Pattern('?op', Pattern.var('x'), Pattern.const(0))
# Our style:
when(_, lambda x: x > 0 and x % 2 == 0, 0)
Any Python expression can be a pattern predicate. This is huge - it means users never hit a wall where the pattern language can't express what they need.
3. Debugging Transparency¶
When this fails to match:
You know exactly why: one of the arguments wasn't a literal. No unification trace needed.
4. Composition Over Framework Complexity¶
Instead of building complex features into the pattern matcher, we compose simple pieces:
# Don't need built-in commutativity support
def commutative(op, value, result):
return [
when(op, value, _).then(result),
when(op, _, value).then(result)
]
The complexity lives in user code, not the framework.
The Deeper SICP Lesson¶
SICP's deepest teaching isn't "build a unification engine." It's:
- Choose the right abstraction level - We chose pattern functions over pattern data structures
- Simple primitives, powerful composition - Our
when().then()composes with all of Python - Build languages - Our DSL emerges from Python, not despite it
- Know what to leave out - Every feature we didn't add makes the system easier to understand
When to Use What¶
Use unification-based systems when: - You need logical inference - Pattern variables must maintain consistency - You're building theorem provers or logic languages
Use our approach when:
- You want transparent, debuggable transformations
- You need to mix pattern matching with arbitrary computation
- You value understanding the entire system
- You're teaching or learning
The Pedagogical Win¶
A student can: 1. Read our ~100 lines and understand everything 2. Start writing rules immediately 3. Never wonder "what's the framework doing?" 4. Use any Python knowledge they have 5. Build complex behaviors from simple rules
This is true to SICP's spirit: simple, composable abstractions that don't hide the mechanism.
Conclusion¶
We built a "worse" pattern matcher that's better for our goals. It's not academically novel - it's just function application. But that's the point.
Sometimes the best design decision is knowing what not to build.
"Everything should be made as simple as possible, but not simpler." - Einstein (attr.)
In our case, SICP's unification would have been simpler than necessary. Function application is just right.