Let’s start by getting the shock out of the way. Smalltalk is an object-oriented programming language. Indeed, Smalltalk is so object oriented that it’s barely a procedural imperative language: Smalltalk’s syntax doesn’t even have conditionals or loops! How is that possible? Well, you don’t need loops in the syntax of your language if block (lambda) objects have a whileTrue method which runs the block repeatedly, and you don’t need conditionals if your boolean objects have an ifTrue method that evaluates its argument only if the boolean was true. If you’re familiar with object-oriented languages that have a less pure approach to object-oriented programming (that is, nearly all of them), then Smalltalk’s style will come as a bit of a shock. But, it doesn’t take much getting used to, and before you’ve grown accustomed to it, you can simply mentally rewrite the conditions and loops you’re comfortable with into Smalltalk’s style.
Smalltalk’s oddness doesn’t stop there, however. Smalltalk is a class-based object-oriented language, as are C++, Java, Python, etc. You might then assume that a reasonable place to start with such a language is “what is the syntax to define a class?” Except... there isn’t one. The Smalltalk standard does not include syntax for classes. Smalltalk was, at its inception, not just a programming language, but an operating system and development environment, and the way that you create a class is through the graphical workspace. You then add methods to the class through user-interface interactions, and then finally, the body of a method has a defined syntax. In order to reduce the semantic leap from languages that you may be more familiar with (and, quite frankly, to make it more gradeable), in this course, we’ll be using an unusual variant of Smalltalk that behaves more like a conventional programming language, GNU Smalltalk. GNU Smalltalk accepts Smalltalk files and has an interactive REPL, like other programming languages. If you’re interested in Smalltalk, you’ll probably want to explore some other Smalltalk environments, such as Pharo ( https://pharo.org/) and Squeak ( https://squeak.org/), but bear in mind that only the syntax for the bodies of methods will look familiar.
This module assumes that you’re familiar with some modern object-oriented languages such as C++ and perhaps Java, and that you’re familiar with programming in general, and focuses on what will be unfamiliar in Smalltalk with that background.
GNU Smalltalk on linux.student.cs.uwaterloo.ca is provided by CS442, in /u/cs442/students. To add it to
your environment, use source /u/cs442/students/env.sh, or simply add
/u/cs442/students/inst/bin to your PATH. Then run the command gst:
$ gst
GNU Smalltalk ready st>
You may also want to install GNU Smalltalk on your own system. In most systems with package managers, it’s in a package named gnu-smalltalk. Or, you can get the source at https://www.gnu.org/software/smalltalk/ and build it yourself. You should get the latest “alpha” version, which at the time of writing this is 3.2.91. Unfortunately, the latest release has a bug printing floating-point numbers; see the discussion in Section 8 on floatfix.st. This bug has been fixed in the version in /u/cs442. Note that the student servers sometimes have /usr/bin/gst as well, but you should make sure to use the version in /u/cs442 because of this bug.
From here, we can write everyone’s favorite first program:
st> 'Hello, world!' displayNl Hello, world! 'Hello, world!' st>
That... kind of worked? It actually worked fine: the first line is the behavior of the displayNl
method itself, and the second line is
the return from the displayNl
method. displayNl
returns the thing that it displayed.
Let’s take a moment to examine how this worked, though, because it probably looks unfamiliar. Our command,
'Hello, world!' displayNl
, has two components: the string and the message. The string should look familiar enough, but it’s worth
noting that in Smalltalk, strings can only be delimited by single quotes ('
), not double quotes ("
). In addition, strings in Smalltalk are
objects of the class String
.
In fact, everything in Smalltalk is an object! Yes, even the class String
itself is an object, of the class Class
! And, of course, the
class Class
is itself an object, of, you guessed it, the class Class
.
The second part is the message. This command sends the displayNl
message to a string object. When an object receives a
message (in this case, displayNl
), it looks up a corresponding method in its class. String objects are of the class String
, so String
’s
displayNl
method is invoked (sometimes written String>>displayNl
to clarify which class’s method is meant), and the result
of the expression ('Hello, world!' displayNl
) is the return value of the method. This whole process should feel
familiar if you’ve used any object-oriented language, so rather than describing the whole process of sending a message
and responding by invoking a method every time, we will simply describe this process as “calling the displayNl
method”1 .
Smalltalk is quite purely object oriented: there are no procedures that aren’t methods, and there is no control flow but calling methods and returning. So, if methods are all that there is, then the way to display a string is to call the display method on the string—i.e., ask the string to display itself. If C++ supported this style of displaying strings, it might look something like this:
("Hello, world!").displayNl();
The parentheses and dots and other syntactic clutter are there because C++ has so many other ways of expressing yourself. When
all you can do is call methods, you don’t need a dot or parentheses to say that you’re calling a method. Of course you’re calling a
method; that’s all you can do! So, all that extra syntax falls away, and the simple way to call a method is just to name it:
'Hello, world!' displayNl
.
Since single quotes delimit strings, if you want to put a single quote in a string, you need to escape it (specify that this single quote is not the end of the string). To do so, simply use two single quotes instead of one:
st> 'This isn''t SUCH a bad programming language!' displayNl. This isn't SUCH a bad programming language! 'This isn''t SUCH a bad programming language!' st>
Note that the string it actually printed had only one single quote. The second line is showing what value was
returned by displayNl
, and to show that a string was returned, it surrounds it in quotes, and escapes the quote
again.
You can use Ctrl+D or the extremely memorable command ObjectMemory quit
to leave the GNU Smalltalk interactive shell. We’ll
mostly be using Smalltalk files, since Smalltalk was never really meant to be used in a read-eval-print-loop (REPL) like
this.
Aside: The Nl
in displayNl
means “newline”. If you didn’t want to print with a newline at the end, just use display
!
Create a file named math.st (or whatever you’d like) in your favorite text editor. Let’s write a program to do some basic arithmetic:
130 + 12 displayNl
And run it:
$ gst math.st
12
That result was probably not what you expected. The reason is that everything in Smalltalk is an object, so all behaviors are
method calls, even +
. It should be clearer if we add some parentheses to show the precedence:
130 + (12 displayNl)
A zero-argument method call like displayNl
binds more tightly (has higher precedence) than a binary method call like +
, so it
came first. Let’s rewrite this in C++-like method-call syntax to show every step:
(30).operator+((12).displayNl());
The displayNl
method was applied on the 12
, and then its result was passed to the +
method on 30
. On the interactive shell, we
would have also displayed the result of the whole computation, which is 42, but now that we’re writing actual Smalltalk files, the only
output that’s displayed is what you explicitly ask for. We can fix this with parentheses (which, contrary to many other languages, are
never method calls). We can also clarify our code with a comment, which in Smalltalk is surrounded by double quotes:
OK! By putting (30 + 12)
in parentheses, we’re forcing it to be evaluated first. Then, the displayNl
method is called on the result,
42
. Now, let’s do some very slightly more sophisticated math:
Once again, a surprising result. If we apply the usual rules of math, then *
has higher precedence than +
, so we would perform
6 * 2
, then 30 + 12
. But, this isn’t (just) math, and +
and *
aren’t just operators, they’re methods. Smalltalk doesn’t actually know the
rules of mathematical precedence, it just knows how to call methods on objects. In this code, it just knows to call the +
method
because you asked it to, and then call the *
method because you asked it to. In fact, Smalltalk doesn’t even know what the
mathematical operators are; if you want to use @
as an operator for your own class, you just need to name a method in that class
“@
”.
Since it knows nothing of the order of operations, Smalltalk treats all binary operators with equal precedence, going left to right. We can now fix our simple math with even more parentheses:
Since displayNl
returns the object being displayed, we can actually observe this left-to-right behavior quite nicely, by printing the
result of every intermediate calculation:
We can perform multiple statements by separating them with a dot (.
). In essence, you can think of the dot like a semicolon (;
) in
C and languages that follow its style:
It is not necessary to end the last statement in a list of statements with a dot, but it is common to do so.
It should come as no surprise that Smalltalk has two boolean values: true
and false
. By this point, it should hopefully also not come
as a surprise that true
and false
are objects of the class Boolean
, and they have some useful methods.
In most modern programming languages, using a boolean to conditionalize execution looks something like this:
1if (condition) { 2 statements; 3}
In Smalltalk, there are no conditional statements like this in the syntax. Instead, just like everything else, we do things
conditionally by calling a method. In this case, the isTrue:
method on Boolean
s:
This example has introduced two new elements of Smalltalk syntax. First, the simple one. Note the :
on ifTrue:
. Methods
named with a colon like this expect an argument. Of course, in this case, the interesting part is what that argument
is.
Now, the more interesting new element. The expression ['true is true!' displayNl]
defines a block. A block is sort of like an
anonymous function, in that it is a packaging of code which can be evaluated later, but this analogy is imperfect, so we’ll simply use
the standard Smalltalk term, “block”. We pass that block as an argument to the ifTrue:
method. Like an anonymous function,
ifTrue:
can then choose to call it, or not to call it.
You may think that the implementation of ifTrue:
has to “cheat” and do a conventional if
statement to work. In fact, its
implementation is far more object-oriented than that. The object true
is actually of the class True
, which is a subclass of the class
Boolean
. Some methods are implemented on Boolean
, and those can be used on true
: like in other class-based languages, subclasses
inherit the behavior of their superclasses. But, the method ifTrue:
is implemented on True
, and all it does is evaluate the
block passed as its argument. It doesn’t need to check if the boolean is true
. It knows it’s true
because this is a
method on the True
class, and only true
is of the class True
. Conversely, the method ifTrue:
on the False
class does
nothing.
A block boxes up code, and it only runs the code if someone asks for it. It gives us a way of making it optional to execute the code at all.
Since both True
and False
support the ifTrue:
method, but ifTrue:
only evaluates its argument on True
, this allows us to
conditionalize the execution of a block, with nothing but objects and methods!
Let’s put this together with our math from before, adding the operators for comparing numbers:
Equality and inequality work on all values, but the rest are specific to numbers. Remember when using these operators that they simply evaluate left-to-right. There is still no operator precedence:
These error messages can be a bit dense, but only the first line really matters: true
doesn’t know how to +
, and because Smalltalk
evaluates left-to-right, we tried to perform + 9
on the true
that was a result of 10 + 10 > 10
. As usual, we can fix this with more
parentheses:
You can build a lot out of ifTrue:
, but it doesn’t have an “else” branch. Booleans also have two other methods, ifFalse:
and
ifTrue:ifFalse:
, which cover other cases:
These hopefully behave as you would expect them to, but note how the call to ifTrue:ifFalse:
looks. First is the operator which
generates the true
on which we’ll be calling the method. Then comes ifTrue:
, then comes the true argument value (a block), then
comes ifFalse:
, and then comes the false argument value (another block). This style of method call syntax is Smalltalk’s second
greatest difference from more popular languages. Because parameter names and argument values can be intermixed in this way, it’s
very important to make sure you put a dot at the end of your statements. If you don’t, the error messages can be extremely
confusing.
Video 1.s.2 ( https://student.cs.uwaterloo.ca/~cs442/W25/videos/1.s.2/): ifTrue:ifFalse:
Generally speaking, Smalltalk programmers try to name their methods such that, with the actual argument values in place, it
reads a bit like an English sentence. For instance, on arrays (which we will look at in more detail later), the method to put a value
into the array at a given location is at:put:
. This would be written my-array at:
the-position put:
my-value. This reads as “in my
array at the position put my value”.
Now we’ve done conditions, but not loops. The simplest way to loop in Smalltalk is over a numerical range:
Note our new addition to blocks. Like methods, blocks can take arguments. Block arguments are labeled with :
at the beginning
of the block, and the list of block arguments is ended with |
.
Of course, this can be mixed with math:
What if I need to do a more sophisticated loop? Like any other programming language, it’s possible to loop while some condition is true (or while some condition is false), but to learn how to do that, first we’ll have to introduce variables, so our condition can actually change.
To declare variables, surround them in pipes (|
). To assign to them, use :=
.
Of course, this isn’t an especially intersting use of variables. Let’s use variables and math to make a more interesting loop:
One major difference from our conditionals (ifTrue:ifFalse:
) is worth noting: our condition itself is in a block! Why did we need
it in a block here, and why didn’t we need it in a block before? The answer is simple, but not obvious: anything that needs to be run
repeatedly needs to be in a block. In order to check whether x < 64
multiple times, we need to put it in a block, so that block
can be made to run repeatedly, re-checking whether x < 64
every time around the loop. Of course, the block given as
an argument to whileTrue:
is also evaluated multiple times! There’s also a whileFalse:
, which behaves as you’d
expect.
Smalltalk has various standard containers, including lists, arrays, strings (which are arrays of characters), and dictionaries.
Although various Smalltalk classes are implemented as lists, the most frequently useful list type is OrderedCollection
. Until now,
we’ve only created objects implicitly—numbers are objects, blocks are objects—but never explicitly built an object from a class. As
with everything, the way to build an object from a class in Smalltalk is to call a method on the class; usually, the new
method. Thus,
we create an OrderedCollection
with OrderedCollection new
:
The display format for an OrderedCollection
is simply OrderedCollection
followed by its elements in parentheses.
Add elements to an OrderedCollection
with add:
:
You can also use addFirst:
to add to the beginning of the list instead of the end. If you have another collection (whether an
OrderedCollection
or not), you can add all of its elements with addAll:
. You can concatenate two lists into a new list with the ,
operator:
Alternatively, you can use with:
(or with:with:
, etc) while building the OrderedCollection
to create a list and add elements in
one step. Loop over the list with do:
:
You can access members of the list directly with at:
, and get the number of elements in the list with size
:
Note that lists—and all other collections in Smalltalk—are 1-indexed, not 0-indexed, so the first element is at: 1
. In this example,
our error was using at: i
after the loop, since at that point, i > lst size
. As before, the first line tells us what we need to know:
“Invalid index 3: index out of range”.
You can also change an element in an OrderedCollection
with at:put:
:
Finally, you can remove the first or last element of a list with removeFirst
or removeLast
respectively. These methods return the
removed element, so are useful for using the list as a queue or stack.
Lists are lists, so indexing with at:
is slow, but adding elements with add:
is fast.
Array
s are of a fixed size, and their size must be declared when they are created. As such, rather than a simple new
method, Array
s
have new:
, which takes the size as an argument:
Note that before we’ve put a value into a slot of the array, the value is nil
. nil
is similar to null, nullptr, or NULL in other
programming languages. But, nil
is a fully-fledged object with methods and a class. You can check if a value is nil
with a normal
comparison, e.g. x = nil
, but it is more common to use the isNil
method, e.g. x isNil
, which returns true
only for
nil
.
Arrays can also be created simply with array literals, which are written with braces, with the elements separated by dots:
Because their size is fixed, Array
s don’t support add:
or its variants. However, every other OrderedCollection
method shown
in the previous section behaves the same as in OrderedCollection
s, including concatenation. Array
s are arrays,
so indexing and mutating with at:
is fast, but adding elements is so slow that the language has simply prevented
it!
It is common to convert back and forth between OrderedCollection
s and Array
s to benefit from each kind’s advantages when
needed. All collections have an asOrderedCollection
method to convert to an OrderedCollection
, and an asArray
method to
convert to an array:
Aside: Array
is actually a subclass of OrderedCollection
which replaces its internal data structure with an array instead of a list.
OrderedCollection
is the generic superclass of all ordered collections, but is also a fully implemented list class itself.
This is why OrderedCollection
has a strange, generic-sounding name, while Array
has a very precise, descriptive
name.
We’ve already seen strings: they’re delimited by single quotation marks ('
). In fact, strings are just a special kind of read-only Array
,
an Array
of Character
s. Anything you can do with an Array
, you can do with a String
, except that you cannot change it:
We can convert a String
to an Array
and vice-versa, usually in order to make it modifiable. To specify an element of the
Character
type, prefix the character with a dollar sign ($):
Program | Output |
| s | s := 'Hello, world!' asArray. s displayNl. s at: 13 put: $?. s := s asString. s displayNl.
| ($H $e $l $l $o $, $ $w $o $r $l $d $! ) Hello, world?
|
Any character after a $ will be taken as a character literal, so, for instance, you can get a literal newline like so:
Using $ like this is usually considered to be in poor form, however, so the Character
class also has a constructor that directly
creates a newline character, lf
:
Video 1.s.4 ( https://student.cs.uwaterloo.ca/~cs442/W25/videos/1.s.4/): Ordered collections
Smalltalk also supports key-value maps, called Dictionary
s. Any value can be used as a key, and a single Dictionary
can contain
keys of multiple types, though most dictionaries in practice only use one.
Create a Dictionary
with new
, and add key-value associations with at:put:
, like other collections:
In addition, you can check whether a key is present with includesKey:
, and remove a key-value pair with removeKey
:
You can loop over Dictionary
s with do:
, like other collections, but the block will only receive the values, not the keys. To loop
over both keys and values, use keysAndValuesDo:
, which takes a two-argument block (which we haven’t seen before):
Note that in two-argument blocks, the space between an argument name and the next :
is mandatory.
Until this point, our focus has been on learning the syntax of Smalltalk and how to use its built-in types. Now, let’s make our own type. We will make a “reverse Polish notation” calculator.
If you’re not familiar with reverse Polish notation (RPN) calculators, they are a form of calculator which represents its input as a list of commands which operates on a stack. A number is a command to push that number to the stack, and an operator is a command to pop two values from the stack, perform the relevant operation, and then push the result onto the stack. For instance, the mathematical expression 1 + 2 ∗ 3 is written in RPN as 1 2 3 * +, and (1 + 2) ∗ 3 is written as 1 2 + 3 *.
Naturally, our RPN calculator will need a stack. It will need a method to push a number onto the stack, and methods for each of the supported operators.
The first question is, how do we create a new class? Naturally, we call a method on an existing class! Namely, the subclass:
method. As there’s nothing in particular our RPN calculator should be a subclass of, we’ll just make it a subclass of Object
, the base
class:
1Object subclass: #RPNCalculator.
We can verify that our new class does in fact exist:
“a RPNCalculator” is simply the output of Object>>display
, the general-purpose display
method that works—albeit not
usefully–on all objects. The odd #
thing is called a “symbol”, and is essentially just how you represent a string that’s
intended to be used as a name in Smalltalk. Other than this one use and in error messages, you’re unlikely to ever need
symbols.
It’s a bit pointless to create a subclass with absolutely nothing in it, of course. You can go one-by-one through the desired methods of the class and add them all, but GNU Smalltalk offers a shorthand for defining a method in a somewhat more familiar style:
1Object subclass: RPNCalculator [ 2 | stack | 3 4 push: aNumber [ 5 stack add: aNumber. 6 ^ aNumber 7 ] 8].
This program creates a class named RPNCalculator
as a subclass of Object
(and note that in this shorthand, no #
is needed),
makes instances of the RPNCalculator
class have the field stack
(actually called an “instance variable” in Smalltalk), and adds a
method push:
which adds its argument to the stack (assuming that the stack is simply an OrderedCollection
). This snippet also
shows how to return a value from a method: with a carat (^
).
We haven’t made our constructor yet, but to do so, we’re going to have to understand one crucial detail about Smalltalk:
Smalltalk instance variables (fields) are always private. There is no syntax to access an instance variable of another object. But, the
constructor is a method of the class, not a method of objects of the class (think of OrderedCollection new
), and so it can’t access
the instance variables even of the object it’s creating. Because of this, it’s common for constructors to be in two
parts: the class has the constructor, and instances have an initializer. The initializer can access its own instance
variables, so the constructor calls the initializer. We’ll demonstrate this by adding a constructor for RPNCalculator
:
1Object subclass: RPNCalculator [ 2 | stack | 3 4 RPNCalculator class >> new [ 5 | r | 6 r := super new. 7 r init. 8 ^ r 9 ] 10 11 " Stack implemented as a list " 12 init [ 13 stack := OrderedCollection new. 14 ] 15 16 push: aNumber [ 17 stack add: aNumber. 18 ^ aNumber 19 ] 20].
There are a few things to note about this example:
new
method. But, new
is not a method on instances of RPNCalculator
, but on the
RPNCalculator
class itself. The way we specify the RPNCalculator
class is RPNCalculator class
. The way we specify
that we’re implementing a method on that class rather than the class we’re currently defining is >>
. Thus, we define
the method with RPNCalculator class >> new
.
new
, which is how you create a new object, we’ve actually lost the ability to create the object.
But, we can call the method we overrode, Object class >> new
, with super
, as super new
.
new
method cannot access the stack
variable of the object it just created, so instead it calls init
.
init
method on line 12 can access stack
, so it creates the needed OrderedCollection
.
push:
method can now safely assume that stack
is an OrderedCollection
.Now, we’d like to be able to do math. Let’s start with addition:
1Object subclass: RPNCalculator [ 2(...) 3 add [ 4 | x y r | 5 y := stack removeLast. 6 x := stack removeLast. 7 r := x+y. 8 stack add: r. 9 ^ r 10 ] 11].
This method pops two elements from the stack with removeLast
, then adds them, then pushes the result onto the
stack, and returns the result. We can—and will—write methods for each of the usual operators, but they’ll all be
doing the same thing: pop two values, do a calculation with them, then push the result. Surely there’s some way we
can write this so that we don’t write the same code over and over again? Of course there is: we need to use blocks!
Blocks allow us to box up a calculation, like an anonymous function, and boxing up the actual calculation so we
can separate it from the stack behavior is exactly what we want to do. We can write a generic “binary operator”
method that takes a block as its argument, and expects the block to do the correct calculation. As yet, although we’ve
passed blocks to other methods, we haven’t actually evaluated blocks ourselves, so we’ll have to learn how to do that,
too.
At this point, you should be able to guess how you evaluate a block. You call a method on it, of course! Specifically, the value
method if it’s a zero-argument block, the value:
method if it’s a one-argument block, and the value:value:
method if
it’s a two-argument block. Our block is supposed to perform a binary operator, so it’ll be a two-argument block:
1Object subclass: RPNCalculator [ 2(...) 3 binary: operatorBlock [ 4 | x y r | 5 y := stack removeLast. 6 x := stack removeLast. 7 r := operatorBlock value: x value: y. 8 stack add: r. 9 ^ r 10 ] 11 12 add [ 13 ^ self binary: [:x :y| x + y] 14 ] 15].
Our new implementation of binary:
is almost exactly like our old implementation of add
, but it takes an operatorBlock
argument, and then calls that block on line 7 with value:value:
. Our add method is now simplified to one line: call the binary:
method on self
(the Smalltalk equivalent of “this
”), with a block as an argument which simply adds together its two arguments. The
result of self binary:
is returned from add
.
Now, let’s add the remaining methods for other mathematical operations, and finish our RPN calculator:
1Object subclass: RPNCalculator [ 2 | stack | 3 4 RPNCalculator class >> new [ 5 | r | 6 r := super new. 7 r init. 8 ^ r 9 ] 10 11 " Stack implemented as a list " 12 init [ 13 stack := OrderedCollection new. 14 ] 15 16 push: aNumber [ 17 stack add: aNumber. 18 ^ aNumber 19 ] 20 21 binary: operatorBlock [ 22 | x y r | 23 y := stack removeLast. 24 x := stack removeLast. 25 r := operatorBlock value: x value: y. 26 stack add: r. 27 ^ r 28 ] 29 30 add [ 31 ^ self binary: [:x :y| x + y] 32 ] 33 34 sub [ 35 ^ self binary: [:x :y| x - y] 36 ] 37 38 mul [ 39 ^ self binary: [:x :y| x * y] 40 ] 41 42 div [ 43 ^ self binary: [:x :y| x / y] 44 ] 45].
Aside: It is generally considered good form in Smalltalk to have many short methods, rather than few long methods. That is, break methods down into their minimal useful components.
Now we have a few options for using and testing RPNCalculator
. First, assuming we’ve stored this in a file named rpncalc.st,
we can use it on the GNU Smalltalk REPL by running gst with the argument -:
$ gst rpncalc.st - GNU Smalltalk ready st> | x | st> x := RPNCalculator new. a RPNCalculator st> x push: 1. 1 st> x push: 2. 2 st> x push: 3. 3 st> x mul. 6 st> x add. 7 st>
As you may guess from that command line, you can also write tests in a separate .st file, and run both with gst rpncalc.st tests.st. Finally, you can write more sophisticated tests using the built in testing framework SUnit ( https://www.gnu.org/software/smalltalk/manual/html_node/SUnit.html).
Video 1.s.5 ( https://student.cs.uwaterloo.ca/~cs442/W25/videos/1.s.5/): RPNCalculator
Our RPNCalculator
is mostly ambivalent to the kind of number you pass in. GNU Smalltalk actually has several ways of storing
numbers: integers, precise fractions, and floating-point numbers. Unfortunately, the different kinds of numbers don’t always behave
well when you mix and match, so if we want our calculator to be robust, we should make sure that incoming numbers are of a
predictable type. We can do this by making subclasses of RPNCalculator
which override push:
, but convert to the desired type:
1RPNCalculator subclass: FractionalRPNCalculator [ 2 push: aNumber [ 3 ^ super push: (aNumber asFraction) 4 ] 5]. 6 7RPNCalculator subclass: FloatRPNCalculator [ 8 push: aNumber [ 9 ^ super push: (aNumber asFloat) 10 ] 11].
Unsurprisingly, asFraction
converts a number to a precise fraction, and asFloat
converts a number to a float.
We can use one of these subclasses by calling FractionalRPNCalculator new
or FloatRPNCalculator new
. Note that we didn’t
need to re-implement new
for each; they inherited their new
from their superclasses. And, we didn’t need to implement binary:
, add
,
sub
, mul
, or div
, for the same reason. If we needed to specialize one or all of these for these types, we could have overridden them, but
all of these types do math the same, so we didn’t need to.
Note that the latest release of GNU Smalltalk has an unfortunate bug in its code to display floating point numbers, and because of this, printing floating point numbers will often fail. This is easily worked around, though, by loading in the float printing code from a later version of GNU Smalltalk. This code is available on the course web site’s assignments tab, named floatfix.st. Load it before your own code, such as gst floatfix.st rpncalc.st. This bug has been fixed in the version of GNU Smalltalk in /u/cs442.
Earlier we said that blocks are similar but not identical to anonymous functions, and the major difference is how they interact with
returning. Blocks themselves evaluate to a value—we used this in our binary:
method—but this isn’t done with ^
, which is how we
return from methods. The value of a block is simply the value from the last statement in the block. On the other hand, when you
return (with ^
) inside of a block, the surrounding method returns, not just the block! For instance, consider the output of this code:
This is particularly useful for making chains of comparisons without getting too deeply nested, or for “switch”-like patterns:
1runCommand: cmd [ 2 (cmd = 'push') ifTrue: [ 3 self push. 4 ^self 5 ]. 6 7 (cmd = 'pop') ifTrue: [ 8 self pop. 9 ^self 10 ]. 11 12 (cmd = 'rotate') ifTrue: [ 13 self rotate. 14 ^self 15 ]. 16]
The display
and displayNl
methods use the displayString
method to convert a value to a string. So if we want our
RPNCalculator
to display its stack when displayNl
is called on it, we could extend it with a displayString
method:
1displayString [ 2 | r | 3 r := 'RPNCalculator ('. 4 stack do: [:x| 5 r := r , (x displayString) , ' '. 6 ]. 7 ^r , ')' 8]
Note that this only affects display
and displayNl
, which are not how values are displayed in the interactive REPL. You can
override printString
for that.
By default, GNU Smalltalk gives stack traces, but nothing else for debugging. However, you can get a debugger (the DebugTools
) by
loading it into your GNU Smalltalk environment along with your own code, and then you will automatically enter the debugger when
there is a problem:
$ gst /u/cs442/students/inst/share/smalltalk/examples/MiniDebugger.st rpncalc.st - Loading package DebugTools GNU Smalltalk ready st> | x | st> x := RPNCalculator new. a RPNCalculator st> x push: 1. 1 st> x push: 0. 0 st> x div. '1 error: The program attempted to divide a number by zero' ZeroDivide(Exception)>>signal (ExcHandling.st:254) SmallInteger(Number)>>zeroDivide (SysExcept.st:1426) SmallInteger>>/ (SmallInt.st:277) optimized [] in RPNCalculator>>div (tmp.st:42) RPNCalculator>>binary: (tmp.st:24) RPNCalculator>>div (tmp.st:42) UndefinedObject>>executeStatements (a String:1) 6 ^self activateHandler: (onDoBlock isNil and: [ self isResumable ]) (debug)
The (debug) prompt acts similarly to gdb: you can go up and down the callgraph, step through code, etc. Use help for a list of commands.
Rather than print, MiniDebugger
has i (for “inspect”), which can inspect objects in great detail.
If you want to step through code easily, you’ll have to cause it to break, so that it enters the debugger. This is most easily done by
adding the statement self halt
to your problematic code.
This course will ask you to use Smalltalk to implement interpreters for (very simple) programming languages. We will not ask you to parse code, and will instead provide parsers for you to use. You are free to ignore our provided parsers and write your own if you’d like, but there’s no reason to do so. Abstract syntax trees will be built manually as Smalltalk objects, and examples given. In general, you should be writing the solution into one .st file, and tests in a separate file loaded afterward, and our input will also work in this way. For example, a simple test case for rpncalc.st might look like this:
1| x | 2x := RPNCalculator new. 3x push: 10. 4x push: 5. 5x div = 2 ifFalse: [ 6 'Division fails!' displayNl. 7].
And, if that’s in the file test.st, would be run as gst rpncalc.st test.st.
You may use only the libraries built into GNU Smalltalk, floatfix.st, and your own code.
The content described in this document should be sufficient for everything needed in this course. If you feel that more functionality would help you, you can look at the GNU Smalltalk Library Reference ( https://www.gnu.org/software/smalltalk/manual-base/html_node/) and User’s Guide ( https://www.gnu.org/software/smalltalk/manual/html_node/index.html). If you’re interested in Smalltalk more broadly, you will probably want to look into a more conventional Smalltalk system such as Pharo ( https://pharo.org/) or Squeak ( https://squeak.org/).
Copyright © 2020–2025 Gregor Richards.
This module is intended for CS442 at University of Waterloo.
Any other use requires permission from the above named copyright holder(s).
© Gregor Richards. | Design by TEMPLATED.