Typing Smalltalk without breaking it
Strongtalk's types are optional, written in angle brackets, and checked separately from running the program. Removing every annotation leaves a program that behaves exactly the same.
Types in angle brackets
A type is written after the name it describes: arguments, temporaries, instance variables and block parameters. A method's result type follows a caret.
at: index <Int> put: value <E> ^<E>
| old <E> |
old := self at: index.
...
Untyped code is simply dynamic: the checker trusts it. You can add types one method at a time, so a large untyped program can be typed gradually.
Protocols, not classes
In most typed object-oriented languages a type is a class. Strongtalk instead separated the two: a type
describes the messages an object understands (its protocol), and an object conforms to it by
understanding those messages, whatever its class. That fits Smalltalk, where unrelated classes often
implement the same messages. Int, Str and Bool are such types.
Self, Instance and unions
| Write | Means |
|---|---|
<Self> | the receiver's own type, so subclasses get the right result type for free |
<Instance> | on the class side, an instance of this class (new ^<Instance>) |
<Int | Str> | either type |
<[Int, ^Bool]> | a block taking an Int and answering a Boolean |
<Array[Str]> | a generic class applied to a type |
guaranteed <Int> expr | tell the checker to trust that expr is an Int |
Generics
Collections are generic. A class declares its type parameters with Generic forAll::
Delta define: #OrderedCollection as: (
Generic forAll: 'E' body: (
(Class subclassOf: 'SequenceableCollection[E]'
instanceVariables: 'array <Array[E]> firstIndex <Int> lastIndex <Int>')))
The original also let methods describe how their result type depends on their arguments, with
inference clauses written in {where ...}; that is how Int + Int could be an
Int while Int + Float is a Float.
What the checker reports
| n <Int> |
n := 'seven'. "assigning String to 'n', which is declared Integer"
n squared: 3 "Integer does not understand #squared:"
(BankAccount owner: 'Ada') deposit: 'lots'
"argument 1 of #deposit:: String is not a Integer"
None of this stops the program from running. In Strongtalk, as in every Smalltalk, the run-time system
catches real errors with doesNotUnderstand:. The checker's job is to tell you about them
early.
Try it
Open the environment, choose Demos › Type checking tour, put the cursor in an example and press Typecheck.
The type checker in this edition is simpler than the original: it follows the class hierarchy
rather than matching protocols structurally, and it uses built-in rules for arithmetic instead of
{where ...} clauses.