Small World: A Little (More) Smalltalk
Lesson 1: Just Browsing
When you begin the Small World
system you will be presented with an application window, which
looks like the following:
Click the button marked Class Browser, and you should get a window
that looks something like the following:
The exact appearence of the window is determined by your operating system,
and may not be exactly as shown here. The class browser is the fundamental
tool used to explore the Small World system. In the simplest, it is
used for evaluating expressions. Try entering the expression 2 + 3
in the text line at the top of the window and hitting the button marked
evaluate expression. The result should appear in the centeral pane,
as follows:
Integers are just one of several literal values in the Smalltalk world. Other types of literals
include real numbers (such as 3.14), characters, strings and booleans.
The notation for character literals is the dollar sign followed by the
character text. Try typing the expression:
$a isLowerCase
String literals are formed using the single quote mark. Strings
can be catenated together using the addition symbol. Try entering the
expression:
'hello' + 'world'
Booleans are represented by the two constants true and
false. You have seen a boolean result already as the response
to the message isLowerCase.
There is another more unusual literal value you will soon encounter.
The value nil is used for a variety of purposes, for example
to represent an otherwise undefined variable.
Comments in Smalltalk are written between double quote marks.
Comments are used to provide documentation for the system, but are otherwise
ignored. Try evaluating the expression
2 " comment here " + "and another" 7 "and finally,
another"
You can use the button marked clear to clear the two text areas.
Messages
Computation in Smalltalk occurs in response to messages. A
message is a request given to an object asking it to perform
an action. The receiver for the message is always written first.
There are three forms of messages in Smalltalk. A unary message
is a word. The the message abs, for example, requests the computation
of the absolute value of a number. Try entering the expression
-4 abs. A binary message is a symbol, such as the plus sign.
You have seen an example of this in the earlier expression 2+3.
Technically, the value to the left of the symbol is the receiver for the
message, and the value to the right is considered an argument; that is,
an additional value used in the execution of the message. The third type
of message is a keyword message. A keyword message is written as
a series of words followed by colons. After each colon is an argument value.
A simple keyword message is between:and:, which tests a value to
see if it is between two endpoints. Try entering the expression:
5 between: 0 and: 10
This message also works with characters, strings, and real
numbers. Try the following:
$c between: $a and: $z
3.7 between: 2.5 and: 4.8
'ben' between: 'alice' and: 'chris'
There is a precedence among messages. Unary messages bind most
tightly, followed by binary messages, and finally keyword messages.
Try entering the following:
-1 + 7 between: -3 abs and: 5 + 2
The expression -1 + 7 will be evaluated first, yielding
6. Next the expression -3 abs will be evaluated, which returns
3. This is followed by 5+2, which returns 7. Finally the
expression 6 between: 3 and: 7 will be executed, which will return
true.
Parenthesis can be used for grouping complex messages in order
to make them easier to read, or when the precedence rules should not
be followed. Try comparing the expressions (4 - 6) abs and 4
- 6 abs, for example. (Make sure you leave a space after the minus
sign, otherwise the parser thinks you mean the negative number -6,
and not the binary operator).
The following illustrate a few of the example messages that
can be evaluated by the Small World system. Try executing each and examining
the result:
2*3 |
'abc' < 'abce' |
'be here how' find: 'here' |
2/3 |
'abc' at: 2 |
true not |
2@3 |
'be here now' break |
7 respondsTo: '+' |
3 rem: 2 |
'2 + 3' doIt |
12 random |
3 quo: 2 |
Window notify: 'how odd' |
12 random |
Classes
All objects in the Smalltalk universe are instances
of a class. A class is a grouping of objects with similar behavior.
You can discover the class of an object by sending it the message class.
Try entering the expression
'abc' class
You should get the expected response String. Try discovering
the class of other literals, such as $c, 3.14, true, and nil.
Now print the class of an integer, such as 7. You will find that
the class of 7 is SmallInt. This stands for Small Integer,
and represents the class of values smaller than 232. Try
comparing
(255*255*255*100) class
and
(255*255*255*200) class
The latter should indicate that the expression is an instance
of LargePositive, which represents large positive integers.
Smalltalk can represent integers of arbitrary size.
You can explore classes and the operations they support using
a class editor. Try selecting the class Char in the list
of classes on the right hand side of the smalltalk browser. If necessary,
you can use the scroll bar to move up and down the list of classes. Then
press the button marked examine class. You should get a window
that looks something like the following:
The list on the right side represents some of the messages that instance
of the class Char understand. Some of these, such as isLowerCase,
you have seen already. Other ones will be new. You can try evaluating
expressions using these messages in the Smalltalk browswer. Explore
the range of messages for some of the other classes, such as String
or SmallInt. You can dismiss a class editor window by pressing
the close button.
Were you surprized at the result of evaluating the expression 2/3?
What is the type (class) of this value? What about 2@3? Whare
are some of the operations recognized by the classes Fraction or Point
?
Classes and Metaclasses
The value 7 is an instance of the class SmallInt.
But SmallInt is itself an object. You can send messages to SmallInt,
just as you can send messages to 7. What is the class of SmallInt?
The answer is that it is an instance of Meta-SmallInt. You can
see the information associated with the metaclass by pressing the button
marked examine metaclass in a class editor.
The idea of a metaclass is used mostly for initialization of
new values. An instance of a class is normally created by the message
new. Try evaluating the expression:
True new
You will see that the result is, as you expect, the value true.
Later, after we have explained methods, try finding the method for new
in the metaclass for True.
Inheritance
We often form abstract categories to describe objects in the
"real world". For example, a Cat is a Mammal which is an Animal which
is in turn a Living Thing. These abstractions allow us to attach information
at an appropriate level of abstraction, for example we know that all
Mammals have hair, and that all Animals breath oxygen. The same type
of organization is used for Smalltalk classes. A character, for example,
is considered to be a special form of Magnitude, an abstract category
for things that can be compared to each other. We say that Magnitude
is a superclass of Char, and that Char is in turn
a subclass of Magnitude. Other things that are magnatudes
include numbers and strings. The class Magnitude is a subclass of
Object, which is an ancestor class for all values in the Smalltalk
universe. (Some people prefer the terms parent class and child class for
superclass and subclass).
As a practical matter, what the superclass/subclass relationship
means is that all methods defined in class Magnitude are also
applicable to instances of subclasses, such as Char and Integer.
This is termed inheritance. We say that the class Char
inherits the methods from class Magnitude, which in turn inherits
from class Object. Inheritance also applies to instance variables,
which we will describe shortly. That is, instance variables defined in
a superclass are also accessible within a subclass.
It is possible for a subclass to have a method with the same name as one
in the parent class. In this case we say that the method in the subclass
overrides the superclass. We will see examples of this in later lessons.
There are two ways to view the inheritance hierarchy. One way
is to evaluate the following expression
Object hierarchy
The result will be a printable representation of the hierarchy
for all classes in the Small World, where inheritance is indicated by
indentation. The result is something like the following:
The message hierarchy is understood by any class, not just Object.
You can find the subclasses of any class using this message. For example,
the expression Integer hierarchy will produce just the portion of
the class hierarchy rooted at the class Integer.
Another way to view the class hierarchy is to press the button marked
examine superclass in the class editor. The result should be
a class editor for the parent class. Try tracing the class hierarchy
for Char in this fashion. You can dismiss the class editor windows
by pressing the button marked close.
Methods
When an object accepts a message it executes a series of actions
that are known as a method. You can view methods by selecting
the message name in the class browser. Try selecting the method isLowerCase
in the class Char. The class browser should then look
something like the following:
If necessary you can resize the window by dragging the lower right hand
corner, or alternatively you can use the scroll bars to view large methods.
When the message isLowerCase is presented to an instance of
class Char this is the statement that will be executed. Methods
have a very simple form, which can be desribed as follows:
heading | optional temporary variables
|
body
The heading includes the message signature, as well as the
names associated with any argument values. If there are any
temporary variables needed for this method they are written between
a pair of vertical bars. We will have more to say about variables
in a moment. The body of a method consists of a series of Smalltalk
statements. A period is used to separate statements, just as in English.
Use class editors to examine a variety of methods, and notice how they
all have this similar form. During your explorations you will undoubtedly
encounter some types of expressions you do not yet understand, but which
will be explained in the next few sections.
Statements
The body of a method is composed of one or more statements.
There are only three types of statements in Smalltalk. The simplest
is an expression statement. An expression is evaluated, and whatever
result it produces (often only nil) is simply thrown away. An expression
statement is normally executed for a side effect, for some other purpose
other than the result. A second type of statement is an assignment statement.
This is used to evaluate and expression and change the value of a variable.
The assignment operator is written as a two character sequence <-.
An example might be something like
x <- 3 + 4
The third type of statement is a return statement. The return
statement ends a method, and indicates a value to be returned to the
originator of the message. The return operator is the up arrow, ^.
A typical return statement might be
^ 'yeah baby'
As noted earlier, periods are used as statement separators
or terminators. It is not necessary to have a period following the
last statement in a method (or in a block, which we will describe shortly).
Traditional control flow statements are handled in a slightly
unusual fashion in Smalltalk. They are actually special cases of expressions.
For example, consider the conditional statement. Try executing the following
statement and examine the result:
(3 < 7) ifTrue: ['happy'] ifFalse: ['sad']
The way this statement is executed is that the receiver is
first determined, and then a message is given to that recieiver. In
this case the reciever is the result of the expression (3 < 7),
which evaluates to the boolen value true. The message ifTrue:ifFalse:
is given to the value true. This message evaluates its first argument,
and ignores its second. (You can see the body of this method in the
class True, as well as the corresponding method in class False,
using the class editor).
The square brackets are used to create a block. A block
is a way to bundle up a series of statements and hold them until you
want them to be executed. In a certain sense a block is like a method:
is encloses a series of statements, and as we will see later, it can
even take arguments. Blocks are used for a conditional because we want
to execute either the true part or the false part, but not both.
Compare, for instance, the execution of the above statement to the following
expression that has the same superficial form:
(3+7) between: (2+3) and: (5 + 10)
Because the latter expression uses parenthesis, the expressions
2+3 and 5+10 will be evaluated before the message
between:and. The ifTrue:ifFalse: message, on the other
hand, uses blocks and hence the evaluation of the arguments is delayed
until after the result of the receiving expression is known.
Blocks are also used in the formation of loops. The following
is a typical while loop (the use of variables, such as those shown here,
will be discussed shortly):
[ i > 0] whileTrue: [ j <- j + i. i <-
i - 1]
Notice that blocks are used for both the conditional and the
body of the loop.
Blocks can have arguments. These are used in certain situations.
For example, consider the following loop used to compute the sum of
the values from 1 to 10:
sum <- 0.
1 to: 10 do: [:i | sum <- sum + i]
The argument to the block is written as an identifier with
a preceding colon. There can be any number of block arguments, which
must be followed by a vertical bar. The remainder of the block is then
written after the bar. In this case, the statement will create a loop
in which the variable i will take on the values from 1 to 10. The body
of the loop then forms the sum of these integers.
Variables
A variable is a name that can be attached to a value. Smalltalk
is a dynamically typed language. This means that types (that is, classes)
are associated with values, and not variables. A variable can at one
point in time be holding an integer, and at another point be holding
a string or a character or some other value. In Small World there
are five varieties of variables:
- Block argument variables. These variables exist as long
as the block is executing. They are given an initial value by the expression
that executes the block (see the lesson on blocks).
- Arguments and Temporary variables. These variable are
created when a method begins execution, and exist as long as the method
is being executed. Arguments are given an initial value as part of the
message, while temporary variables are initially given the value
nil.
- Instance variables. These are created when an instance
of a class is formed. They are used to share information between methods
of a single instance of a class.
- Class variables. These are created when a new class is
formed. They are used to share information between all instances of
a class.
- Pseudo-variables. There are two pseudo-variables that
can be used within a method. The first, self, refers to the receiver
of a message. You have seen an example of this in the body of the method
isLowerCase. The second, super, refers to the receiver
as well, but understands a message as referring to the parent class,
rather than the current class. We will see examples of the use of this
variable in later lessons.
In order to illustrate the use of the latter three categories
we first have to show how to create a class. This will be illustrated
in the next lesson.
Primitive Operations and Arrays
In browsing methods in the Small World you will sometimes run
across expressions such as the following:
^ <34 self arg> " add ourself and the
argument "
The expression between the angle brackets is termed a primitive operation. A primitive operation
is something that cannot be handled in Smalltalk, and is therefore processed
by the underlying virtual machine. There are about 100 primitive operations
in the Small World system. These will be described in the section of
the book devoted to the Small World virtual machine.
Another type of expression you may encounter looks like the
following:
#( (2+3) 'foo' (Pane image: img))
This is termed an array expression.
An array is a block of memory values accessed by their index locations.
An array is normally created by the message new: with an argument that
indicates the number of elements in the array. Each position must then
be filled with a value, as in the following sequence:
t <- Array new: 3.
t at: 1 put: 'hello'.
t at: 2 put: 'new'.
t at: 3 put: 'world'.
This entire sequence can be shortened by using an array expression:
t <- #( 'hello' 'new' 'world' )
Next
Common Errors
Q: I tried evaluating an expression and I get an error window for
an Unrecognized message selector. Why?
A: There are several possible reasons. You might have mistyped the
name of a message selector. You might have left the colon off a keyword
message. You might have misunderstood the precedence involved in an expression
and left out a set of parentheses. Close the window, fix the error, and
try again.
Q: I tried evaluating 7 < 'abc' and got an unrecognized
message selector window.
A: Yes, and is an apple less than or greater than an orange? Numbers
and strings don't mix, and don't speak the same language.
Q: I was messsing around and accidently pressed the delete class
button. Have I destroyed everything?
A: Only if you then pressed the save image button. Otherwise,
just quit the Small World application without saving the image, and then
restart the system. While you are outside of the Small World application
you might think about making a backup copy of the image file, just in case.