Skip to content

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') matches f(a,b) with x=a and x=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:

when('+', is_literal, is_literal).then(lambda a, b: a + b)

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:

  1. Choose the right abstraction level - We chose pattern functions over pattern data structures
  2. Simple primitives, powerful composition - Our when().then() composes with all of Python
  3. Build languages - Our DSL emerges from Python, not despite it
  4. 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.