Chapter 3 - What Makes Pocket Smalltalk Different
Pocket Smalltalk is in some ways a very different kind of Smalltalk
system than what you may be used to. Many of these differences stem
from three important characteristics of this system:
- It is a cross-compiler, that is, it creates
executable programs which run on the PalmPilot rather than
on the host PC.
- It is designed to be compact. Some changes from
traditional Smalltalk have been made to reduce memory
consumption.
- It is declarative. Programs can be represented in
a linear form free of "floating" references.
Declarative Smalltalk
Pocket Smalltalk is an example of a declarative Smalltalk system.
The new ANSI Smalltalk standard is moving toward declarative systems.
Whereas a traditional Smalltalk program is built by extending the
base image, in the process building arbitrary graphs of objects, a
Pocket Smalltalk program is completely defined by its classes
and methods. As a consequence, a Pocket Smalltalk program can be
represented as a simple file in "file-out" format. In contrast, a
traditional Smalltalk program cannot be represented declaratively
at all, and can only be recreated by reapplying the series of
actions used to extend the base image.
Pocket Smalltalk uses a simple and flexible source code management
facility called "packages". A package is simply a file in standard
Smalltalk file-out format. The IDE knows about packages and provides
a user interface for partitioning your classes and methods between
packages. In this way, you can separate base system code from your
own application code. Packages are described in more detail in later
chapters.
Starting Up - the #start method
When your compiled program first starts up, it begins by sending
the selector #basicStart to the class Smalltalk, which by default
resends #start. You must replace the default #start method with
your own in order to get your program running. This is analogous
to the "main" routine in C, and is somewhat unique to this version of
Smalltalk. Most Smalltalk systems do not have any concept of a
well-known entry point, but Pocket Smalltalk programs always begin
this way.
Symbols
Symbols are used for three main purposes in Smalltalk:
- Naming methods (i.e., selectors)
- Naming classes
- Serving as unique "tokens"
A symbol is written as #symbolName, where symbolName is any
valid Smalltalk identifier.
Since symbol data accounts for a large fraction of the total
size of a typical Smalltalk program, Pocket Smalltalk will
replace each symbol by a unique SmallInteger at runtime.
As a consequence, there is no Symbol class, nor is there a way
to distinguish between symbols and integers at runtime.
This is almost never a problem in practice
though, and if necessary, symbols can be "wrapped" in another
class to provide the desired functionality.
This optimization saves a considerable amount of memory space
and execution time. The only real disadvantage, aside from
losing the distinction between Symbols and SmallIntegers, is
that you will be unable to access the characters of a symbol
(for example, in order to print the symbol). Again, this
should not be a problem since Symbols should not be printed at
runtime anyways (Strings should be used for this purpose).
For debugging purposes, you may choose to include the text of
each symbol (select the "emit debug info" option in the IDE).
Then you may use the expression:
Context textOfSymbol: symbol
to recover the String containing the symbol's characters.
The default MiniDebugger uses this feature to provide a symbolic
stack trace when an error occurs.
The System Dictionary
In Pocket Smalltalk, the system dictionary called "Smalltalk"
is actually a class. You cannot look up classes dynamically (using
#at:, for instance) at runtime as you can with other Smalltalk systems.
You also cannot add or remove classes (or methods) at runtime.
Integers
Pocket Smalltalk currently does not provide LargeInteger (arbitrary
size integer) arithmetic, due to the large amount of memory space
required by such a facility. This may be added later as an add-on
package. For now, integers of magnitude -2^31 to 2^31-1 can be
represented in Pocket Smalltalk (i.e. 32 bit signed integers).
Integers between -16384 and 16383 are represented directly as
SmallIntegers and take no object storage. Integers with larger
magnitudes are represented as instances of LongInteger allocated
in the heap. Conversions between the integer types occur
automatically.
Proxies, nil, and doesNotUnderstand:
When an object is sent a message which is cannot interpret,
a Message object representing the message send is created,
and then the message #doesNotUnderstand: is sent to the object
with the Message as the argument.
By default, #doesNotUnderstand: just signals an error, but
certain classes may want to intercept #doesNotUnderstand: and
take some special action.
A class which provides very little behavior of its own, but
instead forwards most messages onto another object is called
a "proxy". Proxies and other classes with similar needs can
subclass from nil instead of from Object (or a subclass of
Object), thereby inheriting none of the "basic" methods defined
by class Object. They may then use #doesNotUnderstand: to
forward messages to another object.
A class inheriting from nil need only implement the one selector:
#doesNotUnderstand:. The cross compiler does not allow you to
create a subclass of nil which does not implement this selector.
become:
The primitive operation Object>>#become: swaps the identity of
two objects. In Pocket Smalltalk this is an efficient operation.
You must be careful with this operation because it can crash the
virtual machine if you "become" the receiver of a method into an
object with fewer named instances variables. The "become" primitive
will fail if you try to convert from a pointerless object (e.g.,
a string or a byte array) to a pointer object, or vice versa.
It will also fail if you try to swap the identities of
SmallIntegers, or if either object is a statically allocated
object (i.e., a class, metaclass, or literal).
The built-in collection classes implement expansion by means of
#become:. This allows a collection to be represented by a single
object, rather than two objects as in some Smalltalk implementations.
Global Variables
Global variables are handled a bit differently in Pocket Smalltalk
than in other Smalltalk systems. Rather than being Associations in
the Smalltalk system dictionary, global variables are class variables
of Object. Since all classes (except root classes; see above)
are subclasses of Object, they all can access these class variables
the same way as global variables.
At compile time, references to class (and "global") variables
are converted into single instructions. No separate objects
are created for global variables; nor can you set the value of
a global variable at compile time (it must be done with some kind
of initialization code at runtime).
Unlike other Smalltalks, classes are not global variables.
For most purposes, you can treat them as such, but the major
difference is that you cannot assign to them. Class names
must not conflict with class variable names.
#ifNil:
A very common idiom in Smalltalk is the following:
object isNil ifTrue: [...]
This expression can be simplified by defining a new message
#ifNil: so that you can write:
object ifNil: [...]
The problem with doing this is that ordinary Smalltalks cannot
apply compiler optimizations to this expression, so the above
code will execute more slowly and take more space than the
usual isNil ifTrue: case. Pocket Smalltalk, however,
knows how to optimize ifNil: and related messages,
so you can use them without any penalty. The message forms
it recognizes are as follows:
- ifNil: [...]
- ifNotNil: [...]
- ifNil: [...] ifNotNil: [...]
- ifNotNil: [...] ifNil: [...]
- orIfNil: [...]
The last message deserves some explanation. orIfNil:
answers the receiver if the receiver is not nil, but if the receiver
is nil it answers the result of evaluating the argument block.
This can be used to provide "default" values for possibly-nil
variables:
^name orIfNil: ['anonymous']
Andrew Brault (ajb@tiac.net)