Thoughts on Smalltalk

I have been using the programming language Smalltalk for my research for almost a year by now, so it’s about time that I talked about it. Smalltalk is a pure object-oriented language from the 1970’s that is one of the founders of the paradigm. I think it’s quite an interesting language, with many unusual advantages over typical languages, even the many languages its paradigm influenced.

There are multiple variants of Smalltalk, but I’ll be mostly talking about Pharo Smalltalk as that’s what I’ve been using by far the most.

Pro: Extreme simplicity

One huge benefit of Smalltalk’s purity is that it is a very simple language – possibly the simplest high-level language I’ve ever used. Virtually every operation in Smalltalk is a message send – basically a method call. Every value in Smalltalk is an object which can accept whatever messages are implemented for it; there are no special ‘primitive types’ like in Java. Pharo only has six reserved words (self, super, nil, true, false, and thisContext) and a few reserved operators (e.g. single quotes for strings, ‘^’ for returns and ‘#’ for symbols).

Even things like if statements and loops are implemented as message sends; True and False are classes, and these messages are implemented by using polymorphism to give True and False different behaviour. For example, ifTrue: accepts a block, and True’s implementation executes it, but False’s does nothing.

This creates a language that is very easy to learn and remember the syntax of. The only real memorization challenge is the standard library, as with any other language, but even then, Smalltalk’s simplicity means you could in theory reimplement anything if you forget & can’t find the library method.

Pro: Easy-to-use function objects

In Smalltalk, functions (blocks) are objects that can be stored in variables and executed with a message send, as in functional programming1. This feature has all the power you would expect if you’ve used a functional language2:

Pro: Customizability

One unexpected thing I liked about Pharo is how customizable it is. Operators are just message sends, meaning that it is not only possible to overload operators, but you can even add custom operators that work the same as any other. You are also allowed to add messages to classes from another package using the extensions system.

One of the first things I made in Smalltalk was a unit converter, and operator overloading made it so I could multiply and divide units with ‘*’ and ‘/’, and they could interoperate with numbers. But perhaps the best example of this in my work is when I added a custom switch statement to the language!

Pharo does not have a switch statement, because generally that isn’t the best option to use: polymorphism or “switch methods” made up of “if a return b” statements are generally more idiomatic. However, for the specific situation I was in, it was best to just have a switch statement. So, I coded a custom switch statement that looks like this:

{
	a < 0 -> 'a is negative'.
	a > 0 -> 'a is positive'.
	a = 0 -> 'a is zero'.
	true -> [ self error: 'This isn''t supposed to happen!' ].
} switch

Best of all, the implementation for this message is just one line of code:

Collection >> switch [
	^self detect: [ :each | each key value ] ifFound: [ :each | each value value ] ifNone: nil.
]

This is already very versatile, but it can be customized further (e.g. raising an error if no clause is true, requiring clauses to be equal to a provided value rather than being true), and it can be done in different ways. All this – and more – is possible thanks to custom operators and extension methods!

Pro: Basic features built-in

While the previous sections have been about Smalltalk in general, Pharo is also great because of its highly featured IDE. There are so many builtin features for things like packaging, dependency management, debugging, and more! You can easily do things like run random code in the Playground, and print or inspect the values of any variables or expressions it contains.

Con: No type or null safety

The first thing I don’t like about Smalltalk is the lack of safety against certain kinds of errors. It is a dynamically typed language, so there is no way to ensure at compile time that you’re passing in the right type of argument to a function. Though I think it’s worth it in this case, as a static type system would add a lot more complexity to a very simple language, that doesn’t remove the downside. Perhaps a statically typed Smalltalk might be better than the current one?

Con: limited interaction with external programs

Another thing I dislike about Pharo is the choice to run it on a VM. There are certainly advantages to this: it’s possible to have multiple environments with different versions, but I think the disadvantages outweigh the advantages.

The major disadvantage of this is that it limits the ability to interact with custom programs. I use Vim controls to edit text whenever I can – once you pass the initial hurdle of learning them, you become much more productive – but I can’t in Pharo because the developers haven’t put in the work to add them. If Pharo was a normal IDE, they wouldn’t need to; I could just edit my code in Vim. The integrations that do exist (e.g. the Git integration) are great, but probably took a lot of effort to make that could have been used for other things.

Also, saving my code to the VM and saving the VM to disk are two different commands, and your changes are only saved if you do both. I’ve lost a lot of code from Pharo crashing or freezing after accidentally running bad code like infinite loops, which erases all your progress since the last VM save.

Conclusion

Overall, Pharo Smalltalk is a great language with a lot going for it, though it’s not perfect. Its pure object-oriented paradigm combined with easy-to-use function objects makes it very simple and customizable, and it has plenty of useful features for development. Its few problems do not exceed its advantages.

Send me feedback about this post


  1. Only one type of function (blocks) works like this, the other (message sends) doesn’t. But it’s easy to make blocks that call messages, so this doesn’t really matter. ↩︎

  2. In fact, the lambda calculus shows that functions alone are enough to make a language Turing-complete! ↩︎