Why Smalltalk is not my favourite programming language

I just read an article that contains a few Alan Kay quotes, and it has reminded me why Alan Kay's mindset has never worked for me. In my teens, I'd read a fair amount of non-specific hype about how Smalltalk was so ahead of its time, and so when I was a university student I went and bought the Smalltalk-80 books to learn more. Sadly, that was before I started writing my book reviews (just over 20 years ago!), so my impertinent opinions were not captured. Instead, I shall write down my views now, that have only solidified over the last couple of decades experience.

I'll start with a couple of horrors that were proudly presented in the books, before moving onto a more philosophical issue.

I can't remember which of the books it was, but there was a code example of a file system written in Smalltalk. This sounded pretty neat! File system code is tricky. Get it wrong, and you'll be corrupting data. You want it to be efficient and performant and reliable, and sitting inside the OS there are usually all kinds of multi-threading issues. Yet it was made to sound straightforward. How could this be? What wonders did Smalltalk provide?

It was about the cheapest, nastiest file system you could imagine. It used block chaining so that each data block contained a pointer to the next block - a scheme I'd only otherwise seen in introductory OS texts because it was so naive. Your data is a power of two in size? It won't neatly fill a set of blocks, then, since each block contains metadata! You want to seek quickly through the file? Bad luck, you need to read all the data from the start! Obviously there's no memory mapping, I doubt there's even caching, and multithreading issues would have been handled by "don't do that".

It felt like there was a fundamental confusion between the simplicity of "a well-designed, simple system" and "hideously inefficient". The ideals of "end-user programming" fall flat in the face of "sometimes you really do need someone to think a little bit and make something good". All of Kay's talk about how we've built rubbish, inefficient computer systems are something I cannot take seriously when this is the example code in the Smalltalk books. The text didn't make excuses for simplified example code or whatever, this was it. I mean, I'm sure Kay could write better code or whatever, but a code example like that's got to be chosen by someone as an exemplar, it tells you something about the language's culture! And it was such a let-down, so Wizard-of-Oz, man-behind-the-curtain.

Some of the most technical content from those books, though, was the description of the Smalltalk-80 implementation. Smalltalk is a wonderfully uniform language, right? Everything's an object, everything's implemented via methods (including loops and conditionals, since chunks of code can also be passed to objects etc.). Even the classes are themselves objects. All pleasantly meta and elegant. And this is all dynamically changeable. You can update methods on core objects, and it will instantly pick up new behaviour. Amazing!

How is this implemented behind the scenes? There's a bytecode interpreter, IIRC (20 years, remember?), and a little compiler down from the Smalltalk to the bytecode. How do we avoid infinite descent when trying to implement the most primitive operations? Well, the compiler understands e.g. booleans, and for the "ifTrue:" and "ifFalse:" methods, it goes and generates the appropriate code. Everything's uniform and all methods overrideable, right? Hahaha. There are special cases hidden behind the scenes that we don't bother the users about, and if they want to dynamically change that behaviour they can't. Simple beats correct.

But does it really matter if you can't dynamically change the behaviour of "true", given that no-one would actually want to do that? This brings me on to the third and more philosophical point: Smalltalk is dishonest about what dynamic change people want, and what dynamic change is helpful.

What most programmers want, most of the time, is a predictable, controlled environment that they can reason about. The biggest trends of the last couple of decades emphasises this: virtualisation and containers allow code to be decoupled from its environment, to have a predictable runtime environment. Source control allows changes to be tracked through time. Both give programmers a chance to reason about the huge pile of state their programs are exposed to. In both cases, people are moving away from wanting to dynamically edit the system they're working on directly and from within that system.

Furthermore, it's rare that people want to dynamically edit everything across the stack. It's rare to want to edit your OS, dev tools, UI system, and app all as a big bundle. It's usually a sign you haven't worked out which layer of abstraction you want to work at (although having said that, tooling exists to allow you to do this in various contexts while maintaining control).

Flexible, script-y, dynamic systems aren't all bad, even if they're usually not my cup of tea. They enable rapid application development, they enable people to solve problems quickly. They're rarely maintainable (and tend to fight with static analyses). I think this is where Smalltalk has its priorities backwards. You want to start with tools that enable comprehension first. Not just "look, I can see the source and edit my window system in real time", but fundamentally source control and virtualisation: Let me see exactly what's changing, and let me debug and work on a system from a system that is not in itself in flux. Build maintainability first.

So, yeah, that's what I found when I dug behind the Smalltalk hype: excessive simplicity and a refusal to engage with the complexity of correct, maintainable and efficient software. Not a lost utopia.

Posted 2024-06-01.