Keep in mind, that this text is only a short introduction -
we recommend reading of a standard textbook on the language for more detailed
information on the language
(-> 'literature').
In contrast to hybrid systems like C++ or Java, everything is an object
in Smalltalk; this includes integers, characters, arrays, classes
and even the programs stackframes, which hold the local variables
during execution.
In Smalltalk, there are not such things as ``builtin'' types or classes,
which are treated different.
+, -, * ... messages.
asUppercase, asLowercase ... messages.
On the other hand, it makes the system very flexible; for example,
it is very easy to extend the numeric class hierarchy by additional
things like Complex numbers, Matrices, Functional Objects etc.
All that is required for those new objects to be handled correctly is
that the respond to some basic mathematical protocol for arithmetic,
comparison etc.
Classes may have zero, one or many instances.
Integer class
Float class
String class
Button class
UndefinedObject class
All instances of a class provide the same message protocol, but typically contain
different internal state.
It is actually the class, which
provides the definition of the protocol and amount of internal
state of its instances.
String class
and respond to the same set of messages. But the internal state of the first
string consists of the characters "h, e, l, l, o" while the second contains
the characters "w, o, r, l, d".
An objects instance variables are only accessable via protocol,
which is provided by the object - there is no way to access an objects
internals except by sending messages to it.
This is true for every object - even for the strings in the example above.
There is no need for the sender of a message to actually know the class of
the receiver - as long as it responds to the message and performs the
appropriate action.
'at:' message. You could write
an ExternalString class, which fetches characters from a file
and returns them from this message.
#basicSize, #identityHash etc.).
Thus, when we send a message to some `normal' object, the corresponding class object provides the behavior - when some message is sent to a class object, the corresponding metaclass provides the behavior.
Since different metaclass may provide different protocol for their class
instances, it is possible to add or redefine class messages just like any other
message.
Smalltalk has no concept of a built-in "new" (or any other built in)
instance creation message - the behavior of those is defined by metaclass protocol.
Every class keeps a table which associates the name of the message (which is called the message selector) to a method. When a message is sent to an object, the classes method table is searched for a corresponding entry and - if found - the associated method is invoked (more details below ...).
Since smalltalk is a pure object oriented language, this table is an object and
accessable
at execution time; it may even be modified during execution
and allows objects to learn about new messages.
(of course, the interactive programming environment heavily depends on
this; for example, the browser adds new items to this table when
a methods source code is accepted ...)
A class inherits all protocol as defined by its superclass(es) and may optionally redefine individual methods or provide additional protocol.
Therefore, a message send performs the following actions (*):
#doesNotUnderstand:) to
the receiver with the message object as argument.
*) For the curious:
All smalltalk implementations use various tricks (caching) to avoid the above
search (also called method lookup) if possible.
In most situations,
the method which corresponds to a selector is reached quickly by an indirect
function call.
more to come about:
... abstract classes
As we will see shortly, smalltalk programs only consist of messages being
sent to objects.
Since even control structures (i.e. conditional
evaluation, loops etc.) are conceptionally implemented as messages,
a common syntax is used in your programs both for
the programs flow control and for manipulating objects.
Once you know how to send messages to an object, you also know how
to write and use fancy control structures.
Smalltalks power (and difficulty to learn) does not lie in the language itself, but instead in the huge protocol provided by the class libraries objects.
Lets start with languages building blocks ...
As a language extension, ST/X also allows end-of-line comments;
these are introduced by the character sequence "/ and treat everything
up to the end of the line as a comment.
examples:
"some comment"
"this
is
a
multiline comment"
"
another multiline comment
"
"/ an end-of-line comment
#new
message sent to a class or the #copy message sent to some instance..
The following literal constants are allowed:
Boolean constants
true, false
UndefinedObject constant
nil
Integer constants
6, -1, 12345678901234567890
8r0777, 16r80000000000, 16rAFFE, 2r0111000
Float) constants
1.234, 1e10, 1.5e15
16r10.1) are allowed, but should not be used
with a radix > 14.
Character constants from the ascii 8-bit character set.
$c
String constants
'foo' or 'a long string constant'
Symbol constants
#bar or #'foo bar baz'
Array constants
#(1 2 $b)
#(1 2 (3 4) ((5 6) 7)).
ByteArray constants
#[0 1 2 3 4]
Identifiers must start with a letter or an underscore character.
The remaining characters may be letters, digits or the underline character (*).
Examples:
foo
aVeryLongIdentifier
anIdentifier_with_underline_characters
For portability with the newest VisualWorks Smalltalk environment, a dollar character ($) is also allowed in an identifier - but not as an identifiers first character.
nil
true and false
self
super
thisContext
here
Since "here" is a Smalltalk/X language extension,
its builtin-ness is less strict than that of the other special variables:
if a variable named "here" is defined, here will refer
to that variable;
otherwise, it refers to the receiver (with different lookup semantics).
- unary messages
- binary messages
- keyword messages
1 negative
illustrates the message "negative" being sent to the number 1. Unary
messages, like all other messages, return a returnValue,
which is simply another object.
true or
false.
If you evaluate this in a workspace using printIt, the returned
value from a positive integer is false.
Unary messages parse left to right, so, for example:
first sends the
1 negative not
negative message to the number 1.
Then, the not message is sent to the returned value.
The response of this second message is finally returned as the value
of the message expression.
If you evaluate this in a workspace using printIt,
the returned value will be true.
Try a few unary messages/expressions in a workspace:
1 negated
-1 negated
-1 abs
1 abs
5 sqrt
1 isNumber
$a isNumber
1 isCharacter
$a isCharacter
'someString' first
'hello world' size
'hello world' asUppercase
'hello world' sort
#( 17 99 1 57 13) sort
1 + 5
This is interpreted as a message sent to the object 1 with the selector '+'
and one argument, the obect 5. Binary messages, like the unary messages
parse left to right. Therefore,
2 + 5 * 3
results in 21, not 17. To change the execution order or to avoid ambiguity you
should place parentheses around:
2 + (5 * 3)
Now, the execution order has changed and the new result will be 17.
Unary messages have higher precedence than binary messages, thus
evaluates as 9 + (16 sqrt), not (9 + 16) sqrt.
9 + 16 sqrt
We highly recommend the use of parentheses - even when the default evaluation order matches the desired order; it makes your code much more readable, and helps beginners a lot.
Try a few binary messages/expressions in a workspace:
1 + 2
1 + 2 * 3
(1 + 2) * 3
1 + (2 * 3)
-1 * 2 abs
(-1 * 2) abs
Note:
Technically, binary messages do not add any new functionality
to the smalltalk language - they are just syntactic shugar and smalltalk
could have easily be defined without them
(i.e. in a Lisp-style,
using keyword messages like 'plus:', 'minus:' etc.)
Note:
The second example above shows why parenthesis are so useful:
from reading the code, it is not apparent, if the evaluation
order was intended or is wrong.
You will be happy to see parenthesis when you have to debug
or fix a program which contains a lot of numeric computations.
5 between:3 and:8
"between:" and "and:" are the keywords,
and the numbers 3 and 8 are the
arguments. The object representing the number 5 is the receiver of the message.
The messages actual selector is formed by the concatenation of all individual
keywords; in the above example, it was "between:and:".
This is different to both a "between:" and a "and:"
message, which often leads to beginners errors.
In the browser, the corresponding method will be listed
under the name: "between:and:".
Keyword messages have lower precedence than either binary or unary
messages. Thus,
is the same as
5 between:1 + 2 and:64 sqrt
5 between:(1 + 2) and:(64 sqrt)
Keyword messages do NOT parse left to right like unary or keyword messages -
if another keyword follows a keyword message, the expression is parsed as
a single message (taking the keywords concatenation as selector).
Thus, the expression:
would send a
a max: 5 min: 1
"max:min:" message to the object referred to by the variable
"a". This is not the same as:
which first sends the
(a max: 5) min: 1
"max:" message, then a "min:"
message to the result.
To avoid ambiguity you should (must) place parentheses around.
Try a few keyword messages/expressions in a workspace:
1 max: 2
1 min: 2
1 + 2 min: 2 + 3
Especially the last example supports the argument for placing
parentheses around expressions - you easily make mistakes if you dont.
1 + 2 min: 2 + 3 negated
1 negated
"negated" to the number 1, which gives
us a -1 (minus-one) as result.
1 negated abs
"negated" to the number 1, which gives
us an intermediate result of -1 (minus-one);
then, the message "abs" is sent to it, giving us
a final result of 1 (positive-one).
-1 abs negated
"abs" to the number -1 (minus-one), which gives
us a 1 (positive one) as intermediate result. Then this object
gets a "negated" message.
1 + 2
"+" to the number 1, passing it
the number 2 as argument. The returned object is 3.
"+" message.
1 + 2 + 3
"+" is sent to the number 1, passing it
the number 2 as argument. Then, another "+" message is sent to
the intermediate result, passing the integer-object 3 as argument.
1 + 2 * 3
-1 abs + 2
"abs" to the number -1 (minus-one), then sends "+"
to the result, passing 2 as argument.
1 + -2 abs
"abs" to the number -2, then sends "+"
to the number 1, passing the result of the first message as argument.
-1 abs + -2 abs
"abs" to the number -1 (minus-one) and remembers the result.
Then sends "abs" to the number -2 and passes this as argument
of the "+" message to the remembered object.
1 + 2 sqrt
"sqrt" to the number 2, then passes this as argument
of the "+" message to the number 1.
(1 + 2) sqrt
"+" to the number 1, passing 2 as argument.
Then sends "sqrt" to the result.
1 min: 2
"min:" (minimum)
message to the number 1, passing 2 as argument.
(1 max: 2) max: 3
"max:" (maximum)
message to the number 1, passing 2 as argument. Then sends "max:"
to the returned value, passing 3 as argument.
(1 + 2 max: 3 + 4) min: 5 + 6
"+" to the number 1 passing 2
as argument and remembers the result.
Then, "+" is sent to the
number 3, passing 4 as argument.
Then, "max:" is sent to the remembered first result,
passing the second result as argument. The result is again
remembered.
Then, "+" is sent to the number 5, passing
6 as argument.
Finally, the "min:" message is sent to the
remembered result from the first max: message, passing
the result from the "+" message.
1 max: 2 max: 3
"max:max:"
message to the number 1, passing the two arguments, 2 and 3.
"max:max:" message,
this leads to an error (message-not-understood).
This example illustrates why parenthesis are highly recommended - especially with concatenated keyword messages.
'hello' at:1
"at:"
message to the string constant.
'hello' , ' world'
","
binary message to the first string constant, passing another string as argument.
'hello' , ' ' , 'world'
","
binary message to the first string constant, passing ' ' as argument.
Then, the result gets another "," message, passing 'world' as
argument.
#(10 2 15 99 123) min
"min"
unary message to an array object (in this case: a constant array literal).
All collections respond to the min message, by searching for the smallest
element, and returning it.
Every variable is automatically initialized to nil when created.
For now, only some global variables and local variables are described (because we need them for more interesting examples); the other variable types will be described later.
Beside classes, a few other objects can be referred to via a global; the most interesting for now is:
Transcript
show:something
cr
showCR:something
show: followed by cr.
A local variable declaration consists of an openimg '|' (vertical bar) character,
a list of identifiers and a closing '|'. It must be located before any
statement within a code entity (a block or method, which are described below).
For example:
declares 3 local variables, named 'foo', 'bar' and 'baz'.
| foo bar baz |
A local variables lifetime is limited to the time the enclosing context is active - typically, a method or a block (you will learn later, what a block is).
Notice, that when a piece of code is evaluated in a workspace window, the system generates an anonymous method and invokes it for execution. Therefore, a local variable declaration is also allowed with doIt-evaluation (the variables lifetime will be the time of the execution).
Instance variables are private to some object and their lifetime is
the lifetime of the object.
We will come back to instance variables, once we have learned how classes
are defined.
foo := 1
or:
bar := 'hello world'
This makes the variable refer to the object as specified
after the assignment symbol - which may either be a literal (i.e. a constant),
the value of some other variable, or the outcome of some message expression.
Keep in mind, that only a reference to that object is stored into the variable,
not the object itself. This means, that multiple variables may refer to the
same object.
For example:
the previous example demonstrates, that both var1 and var2
refer to the same array object.
I.e. that in smalltalk, a variable
actually holds a pointer references to objects.
|var1 var2|
"/ ask the Array class for a new intance
"/ with 5 elements ...
"/ ... and assign it to var1
var1 := Array new:5.
"/ and also to var2
var2 := var1.
"/ now show what those vars refer to in the transcript
Transcript show:'var1 refers to: '.
Transcript show:var1.
Transcript cr.
Transcript show:'var2 refers to: '.
Transcript show:var2.
Transcript cr.
"/ change the 2nd element ...
var1 at:2 put:1.
"/ show what those vars refer to
Transcript show:'now, var1 refers to: '.
Transcript show:var1.
Transcript cr.
Transcript show:'now, var2 refers to: '.
Transcript show:var2.
Transcript cr.
Multiple assignments are possible, for example:
binds both 'foo' and 'bar' to the same string-object.
foo := bar := 'hello'
Be careful when assigning to globals - do not (by accident) overwrite
a reference to some other object:
To prevent beginners from
doing harm to the system, ST/X checks for this situation
and gives a warning - however, other (smalltalk-) systems may silently
perform the assignment and leave you with an unusable system.
Float := nil
As a general rule:
do not assign to global variables - its usually a sign of
bad design if you have to (as you will see below, there are other variable
types which can be used in most situations).
Knowing about variables, we can try more interesting messages:
ask the Float class for the π constant:
ask the
Float pi
Transcipt object to flash its view:
ask the
Transcript flash
Workspace class to create a new instance and open
a view for it:
declare a local variable, assign a value and display it on the transcript
window:
Workspace open
remember, that variable may refer to any object.
Thus, the following is legal (although not considered a good style):
|foo|
foo := -1.
Transcript show:'foo is now bound to: '.
Transcript show:foo.
Transcript cr.
foo := foo + 2.
Transcript show:'foo is now bound to: '.
Transcript show:foo.
Transcript cr.
|foo|
foo := -1.
Transcript show:'foo is: '.
Transcript show:foo.
Transcript cr.
foo := 'hello'.
Transcript show:'foo is now: '.
Transcript show:foo.
Transcript cr.
| coll |
coll := Set new. "/ create an empty Set-collection
coll add:'one'.
coll add:'two'.
coll add:3.
A cascade expression sends another message (possibly with arguments) to
the previous receiver.
| coll |
coll := Set new. "/ create an empty Set-collection
coll add:'one'; add:'two'; add:3.
-1 negated.
1 + 2.
first sends the 'negated' message to -1 (minus one), ignoring the result.
Then, the '+' message is sent to 1 (positive one), passing the number 2
as argument.
[For non-Smalltalkers:
As a first approximation, regard a block as a reference to
an anonymous function, which can be defined without a name, passed
to other objects and eventually executed.]
A block is defined simply by enclosing its statements in brackets,
as in:
later, when the block is to be evaluated,
send it the
| someBlock |
someBlock := [ Transcript flash ].
#value message:
Blocks may be defined with argument(s);
...
someBlock value.
...
A block with argument(s) is defined by giving the formal argument identifiers
after the opening bracket - each prepended by a colon-character. The list
is finished by a vertical bar.
For example:
defines a block which expects (exactly) one argument.
|someBlock|
...
someBlock := [:a | Transcript showCR:a ].
...
To evaluate it, send it the value: message, passing the desired
argument object.
For example, the above block can be evaluated as:
(here, a string-object is passed as argument).
someBlock value:'hello'
Blocks can be defined to expect multiple arguments, by declaring each
formal argument preceeded by a colon. For evaluation, a message of the form
value:...value: with a corresponding number of arguments must
be used.
For example, the block:
can be evaluated with:
|someBlock|
...
someBlock := [:a :b :c |
Transcript show:a.
Transcript show:' '.
Transcript show:b.
Transcript show:' '.
Transcript show:c.
Transcript cr
].
...
someBlock value:1 value:2 value:3
When evaluated, the return value of the message is the value of the
blocks last expression.
I.e.:
assigns 6 to the variable 'result'.
|someBlock|
...
someBlock := [:a :b :c | a + b + c].
...
result := someBlock value:1 value:2 value:3.
...
Blocks have many nice applications: for example, a GUI-Buttons action can be define with a block, or a timer may be given a block for later execution.
However, the most striking application of blocks is in defining control
structures (like if, while, repeat, loops etc.) as known in other languages.
Recall, that the above description of the smalltalk language did not
describe any syntax for control - the reason is simple: there is none.
Instead, all program control is defined by appropriate message protocol;
mostly in the Boolean and Block classes.
ifTrue: / ifFalse:
protocol as implemented by the boolean objects true and false:
ifTrue: aBlock
ifFalse: aBlock
ifTrue:trueBlock ifFalse: falseBlock
ifFalse: falseBlock ifTrue:trueBlock
So, to compare two variables and send some message to the Transcript
window, you can write:
of course, you may change the indent to reflect the program flow;
this is what a C-Hacker (like I used to be) would write:
...
(someVariable > 0) ifTrue:[ Transcript showCR:'yes' ].
...
and that is what a Lisper (and many Smalltalkers) would use:
...
(someVariable > 0) ifTrue:[
(someVariable < 10) ifTrue:[
Transcript showCR:'between 1 and 9'
] ifFalse:[
Transcript showCR:'positive'
]
] ifFalse:[
Transcript showCR:'zero or negative'
].
...
...
(someVariable > 0)
ifTrue:[
(someVariable < 10)
ifTrue:[
Transcript showCR:'between 1 and 9']
ifFalse:[
Transcript showCR:'positive']]
ifFalse:[
Transcript showCR:'zero or negative'].
...
whileTrue: loopBlock
whileFalse: loopBlock
whileTrue
whileFalse
|someVar|
someVar := 1.
[someVar < 10] whileTrue:[
Transcript showCR:someVar.
someVar := someVar + 1.
]
Warning:
"(someVar < 10)" would return a boolean, which does
not implement the while messages).
do: aOneArgBlock
|anArray|
anArray := #( 'one' 'deux' 'drei' 'quatro' 5 6.0 ).
anArray do:[:el | Transcript showCR:el ].
of course, you should indent the code to reflect control flow;
we use the C-style indentation, as in:
anArray do:[:el |
Transcript showCR:el
].
There are many, many useful enumeration messages provided in the collection
classes, and we highly recommend that you have a look at them.
#collect:,
#detect:, #select:, #findFirst: etc.
Now, we reached a point, where we realize that the key to becoming a Smalltalker lies in the knowledge of the systems class library. Although this is true for all big programming systems, it is even more true for smalltalk, since even control structures and looping is implemented by message protocol as opposed to being a syntax feature.
No programming is possible if you dont
know the protocol of the classes in the system, or at least part of it.
To give you a starting point, we have compiled a
list of the most useful messages as implemented by
various classes in the
``list of useful selectors''
document.
Copyright © Claus Gittinger Development & Consulting
Copyright © eXept Software AG
<cg@exept.de>