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:
- As mentioned above, they are used to implement if statements and loops.
- They allow Smalltalk to have both short-circuit and non-short-circuit and and or operators: passing a block yields a short-circuit operator, passing a value leads to non-short-circuit.
- They make code more explicit. Everything that could execute later than where it is at is in a block. Short-circuit operators in most other languages, for example, do not have this property.
- They are very useful for managing collections and streams of objects. do: takes a block and runs it once for each object in the collection, and can be used to implement most other collection operations. collect: maps one collection into another based on a mapping function (e.g. add 1 to everything in this list). select: and reject: allow you to select some elements from a collection that meet a provided condition. inject: and fold: allow you to combine a list into one element based on a binary operator (e.g. find the sum of a list of numbers, concatenate a list of strings).
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.