Objects at the Command Line — Why I'm Rewriting Everyday Scripts in Smalltalk
We’ve quietly agreed on a split that I don’t think holds up.
Scripts are supposed to be quick, procedural, disposable — a function, a loop, a couple
of ifs, ship it. Objects are for the “real” codebase: the domain model, the layers,
the architecture diagram someone drew once.
I’ve been writing GNU Smalltalk for a while now, and that split has stopped making sense to me. So I’m starting a weekly series — Objects at the Command Line — where I take a small, familiar script and rebuild it in a language where everything is an object, then pull out the one lesson that transfers back to whatever you write on Monday.
First, thirty seconds that reframed it for me
In most languages, if is a keyword. The compiler knows about it. It is part of the grammar.
In Smalltalk, it isn’t. Booleans are ordinary objects, and ifTrue:ifFalse: is just a
message you send to one:
Transcript showCr: 'the class of (3 > 2) is: ', (3 > 2) class name.
Transcript showCr: ((3 > 2)
ifTrue: [ 'ifTrue:ifFalse: is a message' ]
ifFalse: [ 'this block never runs' ]).
Run it (gst demo.st):
the class of (3 > 2) is: True
ifTrue:ifFalse: is a message
3 > 2 doesn’t evaluate to some primitive flag — it returns an instance of the class
True. And True knows how to respond to ifTrue:ifFalse: by running the first block.
False knows to run the second. There is no branching machinery in the language at all.
There are just objects answering messages.
Once you’ve seen that, you start noticing how much of what we call “control flow” is really just dispatch wearing a costume — and how much of our code branches on things that would rather be objects.
What you get each week
A short post, one image, and one idea:
- A real script, shown fairly in a language you already know — Python, JavaScript, Ruby, Bash. Rotating, deliberately, so the point never depends on your stack.
- The same idea rebuilt with objects and message passing, side by side.
- One transferable lesson you can use immediately, in whatever language you actually write.
Three promises
Because a series like this only works if you trust it:
- No language wars. This is not “Smalltalk good, your stack bad.” The original is always shown at its idiomatic best, its author credited, and every post names where the original is the better real-world choice. If I have to strawman your language to make my point, I don’t have a point.
- Every line of code runs before I publish it. Both sides. I’ve already caught real bugs this way — a method that doesn’t exist in GNU Smalltalk 3.2.5, a parsing trap in the very snippet above. Broken code in a post about clarity would be its own punchline.
- Short and concrete. One idea, one image, one takeaway. No 3000-word manifestos (this post is the exception, and it’s the only one).
The roadmap
This is the index — I’ll link each episode here as it publishes. The later entries are intentions, not contracts; if a better example shows up, it wins.
| # | Episode | Before | The idea |
|---|---|---|---|
| 001 | FizzBuzz without an if |
Python | Conditionals become objects; if is a message |
| 002 | A state machine with no switch |
JavaScript | States are objects, events are messages |
| 003 | A calculator with no operator table | Ruby | Operators are messages the number already answers |
| 004 | Game of Life has two rules | Bash | Drowning in bookkeeping? You’re missing an object |
| 005 | Roman numerals | Perl | Modeling a domain vs. branching on cases |
| 006 | Retry with backoff | — | A policy object vs. nested try/loops |
| 007 | Markov chain text | — | Collections and blocks as design tools |
| 008 | A tiny templating engine | — | Classic OO patterns, felt live |
Why GNU Smalltalk?
Partly because I maintain a VS Code extension for it, so the tooling in these posts is tooling I actually use. But mostly because Smalltalk is uncompromising. There’s no escape hatch back to keywords and special forms — if you want a conditional, you send a message; if you want a loop, you send a message. That makes an idea impossible to un-see.
The honest part: GNU Smalltalk is not what you should reach for to parse your CI logs tomorrow. The library ecosystem is thin, the community is small, and Python will beat it on raw “get this done by lunch” every time. That’s fine. You may never write a line of it — and the way it thinks will still change how you write everything else. That’s the whole bet of this series.
Where this goes past toy examples
Fair objection: FizzBuzz and Game of Life are small. Does any of this survive contact with a real problem?
I’m finding out in the open. Alongside this series I’m building Parley — a package manager for GNU Smalltalk, written in Smalltalk. Its tagline is “Smalltalk packages, resolved by conversation,” and that’s not decoration. Most package managers treat a dependency conflict as an error string. Parley models resolution as a negotiation between independent parties: versions, ranges, and constraints are objects that talk to each other, and when they can’t agree you get an inspectable derivation tree explaining why — not a stack trace.
That’s this series’ idea at a scale where it actually costs something. The same instinct that
turns a FizzBuzz if-ladder into a list of rule objects turns “dependency hell” into a
conversation you can audit.
The honest part: Parley is pre-alpha. There’s nothing to install yet — the work right now
is the algebraic core (Version, VersionRange, VersionConstraint) and the tests that
prove its laws hold. I’m mentioning it because building it in public is half the point, not
because you should go download it today.
Follow along
New episode weekly. If “object-oriented scripting” sounds like a contradiction to you — good. That’s exactly the itch I want to scratch.
What’s a small script you’d love to see rebuilt with objects? Tell me, and it might become an episode.