Stop Worshipping Classes — Your Code Needs Verbs
Why I’m using a Lisp and Julia pattern (multiple dispatch) to write my most complex Python code
Why I’m using a Lisp and Julia pattern (multiple dispatch) to write my most complex Python code
Introduction — The Hook
What if I told you that Python’s biggest strength — its class system — is also its biggest trap?
We build software as a zoo of nouns: User, Document, Invoice, Report.
Each class owns its methods, guards its little world, and exposes a polite interface like user.save(), doc.analyze(), invoice.export().
It feels organized. It feels professional.
And then, six months later, you try to add a new behavior that touches several of these classes at once — and the whole thing begins to creak.
Suddenly, your architecture sounds less like clean jazz and more like a marching band warming up.
In my previous article, we learned how multiple dispatch (via the plum library) can replace 500 lines of if isinstance with a single elegant function.
Today, we go deeper. This is not just about refactoring — it’s about re-thinking how we design Python itself.
The Noun-Centric Trap
Let’s start with something familiar.
A typical web app defines these classes:
class User:
def save(self): ...
def notify(self): ...
class Document:
def analyze(self): ...
def export(self): ...
class Report:
def generate(self): ...
def email_to(self, user): ...Looks fine, right?
Now, let’s imagine we want to implement a new feature: process a user and a document together — maybe generating a personalized analysis.
Where does this function belong?
- Inside
User? ThenUsermust suddenly know how documents work. - Inside
Document? Then it must start understanding users. - A third option: some utility module that imports both classes and tangles your dependencies.
Whichever you choose, you’ve broken the illusion of modularity.
Each class pretends to be independent until you need them to cooperate — and that’s when you realize Python’s OOP model is noun-centric.
In a noun-centric world, data owns behavior.
But real systems are verbs interacting with nouns.
That tension is what makes so many “clean” architectures silently rot.
The Verb-Centric Freedom
Now imagine flipping the design.
Instead of asking, “which class owns this behavior?”, we ask, “what verb describes this behavior?”
from plum import dispatch
@dispatch
def process(user: User, doc: Document):
print(f"Processing {doc} for {user}")That’s it.
We define the verb first — and let types fill in the details.
Want to support another combination? Just write another dispatch:
@dispatch
def process(user: User, report: Report):
print(f"Generating report for {user}")There’s no hierarchy, no awkward cross-imports, no “manager” class trying to know everything.
Behavior now lives with the verb, not the noun.
Suddenly, your design breathes again.
You can add new types or new verbs without touching the old code — the very thing object-oriented design struggles with.
The Killer Example
Let’s switch domains — to something visual.
You’re building a small 2D space game.
You have three entities:
class Asteroid: ...
class Ship: ...
class SpaceStation: ...In traditional OOP, you’d write methods like:
class Ship:
def collide(self, other):
if isinstance(other, Asteroid):
print("Ship destroyed!")
elif isinstance(other, SpaceStation):
print("Docking sequence started.")It works — until you add Asteroid.collide(Asteroid), or Ship.collide(Ship), or Asteroid.collide(SpaceStation).
You soon realize each class must know about every other one.
Your once-modular code is now a diplomatic nightmare.
Now, watch what happens with multiple dispatch:
from plum import dispatch
@dispatch
def collide(a: Asteroid, b: Ship):
print("Ship destroyed!")
@dispatch
def collide(a: Ship, b: SpaceStation):
print("Docking sequence started.")
@dispatch
def collide(a: Asteroid, b: Asteroid):
print("Asteroids shatter and scatter.")The same logic — no branching, no tangled if isinstance, no internal cross-knowledge.
You can define each interaction in its own file.
Add a new entity tomorrow? Just create new combinations.
This is something traditional Python OOP simply cannot express cleanly.
You’ve just written type-oriented interaction logic — something that languages like Julia and Lisp have mastered for decades, and that Python can emulate beautifully with dispatch.
The Real Secret
The real secret isn’t plum.
It’s decoupling data from behavior.
Your classes don’t need to be miniature kingdoms with dozens of methods.
They can just be dataclasses — light, dumb, and stable.
from dataclasses import dataclass
@dataclass
class Person:
name: str
age: intAll the interesting stuff — saving, validating, exporting — can live in independent, dispatch-driven verbs:
@dispatch
def save(person: Person):
print(f"Saving {person.name}")
@dispatch
def validate(person: Person):
if person.age < 0:
raise ValueError("Invalid age")Tests become trivial.
Maintenance becomes local.
Adding new types or operations no longer risks breaking existing ones.
When data and verbs are decoupled, your system stops being a brittle castle of classes — and becomes a network of composable behaviors.
That’s the deeper pattern behind multiple dispatch: composition through verbs.
Conclusion — Let Your Functions Be Free
Python’s classes are fantastic for holding state.
But for behavior — the logic that evolves, multiplies, and interacts — classes become cages.
A verb-centric architecture restores balance:
- Data stays simple.
- Behavior scales independently.
- You gain the open–closed principle not as a slogan, but as a living property of your system.
So stop writing “manager” classes.
Stop asking “who owns this method?”
Start asking, “what verb does this system need?”
Because once you do, you’ll realize:
Python never lacked power — it just needed a few more verbs.
Do you like this kind of thinking?
- Follow me on X: @multiplier31 for tweets on developer thinking, science, investing/trading, and more.
- And on Medium: @gwangjinkim for deep dives on Python, Lisp, system design, and developer thinking, and much more
- Subscribe on Substack: gwangjinkim.substack.com — coming soon with early essays, experiments & newsletters (just getting started).
- Visit my Ghost blog (here): everyhub.org — with hands-on tech tutorials and tools (most of them I cross-post here in medium, but not all of them).
Follow anywhere that fits your style — or all three if you want front-row seats to what’s coming next.