[up]

Introduction to Smalltalk

Copyright February 2002, Ivan Tomek
(comments most appreciated at ivan.tomek@acadiau.ca, www.).

Disclaimer:
This is an unsolicited contribution and not an official component of the environment. All errors and inconsistencies are my responsibility and I do not assume any legal responsibility for any errors.

The text was originally written for VisualWorks by Ivan Tomek, and intended to be read in a VisualWorks workspace. The transcription into HTML and inclusion of Smalltalk/X differences was done by Claus Gittinger.

Preface

This text introduces the main concepts of Smalltalk in general, and both VisualWorks Smalltalk and Smalltalk/X in particular. It explains the language, the environment, its essential components, and the principles of developing a complete Smalltalk application. The text is interactive and you can execute all examples.

I use the following colors to emphasize different types of information:

Blue for executable code and program-related information.
Red for environment commands appearing, for example, "Print it" in popup menus.
Black for all other text. Important words are underlined or bold.
Green for naviagion links.

A Note on the HTML transcription and Smalltalk/X adaption

Wherever VisualWorks and Smalltalk/X differ, I have left the original (VisualWorks) text unchanged, and added the corresponding ST/X text in a gray color.
For example:
...the Debugger is closed by pressing the terminate (abort) - Button.
This means, that under VisualWorks, the corresponding button is labelled "terminate", while in Smalltalk/X its label is "abort".

The original text was used in a workspace, and editable - this html transcription is readonly; however, you can copy text into a workspace and try a modified version of the example there (try the right button menu, while the mouse pointer is over some example text). Also, if you read this document inside the Smalltalk/X help-viewer, you can click on most code examples and see the result in the info area at the bottom of the document reader window.

Contents

This Introduction has the following parts:

- Part 1: Why Smalltalk?
- Part 2: Objects and messages
- Part 3: Objects and classes
- Part 4: Defining and editing classes and methods
- Part 5: Essential classes and methods
- Part 6: Other important classes
- Part 7: Creating graphical user interfaces
- Part 8: Developing a Smalltalk application
- Part 9: What next
- References

Part 1: Why Smalltalk?

Why should you explore Smalltalk? In short, because it is a very simple and very powerful language and environment. As a more detailed answer, I think that the following are good reasons to learn about this unique programming language and environment:

In the following sections, I will introduce enough Smalltalk so that you can start developing simple applications and, more importantly, explore further easily.

Part 2: Objects and messages

1. Everything in Smalltalk is an object, and all work is done by sending messages to objects.

Everything in Smalltalk, including numbers, strings, windows, the compiler, and the parts of the interactive development environment is an object. Objects communicate by sending messages to one another and every message returns an object.
Everything in Smalltalk happens by sending a message to an object.

In a Smalltalk expression, such as

    'abc' reverse
the receiver object that executes the message (here the string 'abc') always comes first, the message (here reverse) follows. Think of a Smalltalk expression as an order to the receiver to execute a message. Just as you might say in English
    dog bark
or
    car go
the expression 'abc' reverse is "an order to the receiver to 'reverse' itself".

The following are examples of valid Smalltalk expressions; the text surrounded by double quotes " are comments and ignored by Smalltalk.

    3 squared               "Receiver is the integer object 3; the message is squared.
			     It is an order to the object 3 to calculate and return its square."
    'abc' asUppercase       "Receiver is the string object 'abc', the message is asUppercase.
			     It is an order to object 'abc' to calculate and return its uppercase form."
    200 factorial           "Receiver is the integer object 200, the message is factorial.
			     Read it as an order to 200 to calculate and return its
			     factorial: 200*199*198*..*1."
To execute these examples one-line-at-a-time and see the results, proceed as follows:

In a VisualWorks Workspace or if the text has been typed or copied into a Smalltalk/X Workspace:

In the Smalltalk/X document reader:

Do not try to execute the three lines together - Smalltalk would treat them as one big but inconsistent expression. More on this later.

By the way, you can type and evaluate Smalltalk expressions in any text space, not just the Workspace. However, I recommend that you open a new workspace for your additional experiments by executing the command sequence Tools -> Workspace in the main Launcher window (select Tools in the menu and then click the Workspace command).

2. Smalltalk recognizes three kinds of messages: unary, binary, and keyword

There are only three kinds of messages in Smalltalk: unary, binary, and keyword messages. The difference between them is in the form of the name of the message (its selector) and the number of arguments that that they expect.

Unary messages consist of a single 'word' (properly called an identifier); they don't have any arguments. Evaluate the following three examples line-by-line with "Print it".

    3 negated                   "Receiver is 3, message selector is called negated"
    27 squared                  "Receiver is 27, selector is squared"
    'black fox' asUppercase     "Receiver is 'black fox', selector is asUppercase"
Binary messages use one or two (ST/X: or three) special characters such as + - / \ @ , & | and @ for selector, and the selector is followed by one argument.
Examples are
    3 + 5                       "Receiver is the integer number 3, selector is +, argument is the integer object 5"
    'abc' , 'xyz'               "Receiver is 'abc', selector is , and argument is the string object 'xyz'"
    'one' -> 'eins'             "Receiver is 'one', selector is -> and argument is the string object 'eins'"
    50 @ 100                    "Receiver is 50, selector is @ and argument is 100"
    17 // 5                     "Receiver is 17, selector is //, argument is the integer 5"
Keyword messages allow any number of arguments. The selector consists of one or more keywords (a keyword is an identifier followed by a colon), and each keyword is followed by argument:
    3 between: 5 and: 10        "Receiver is 3, message selector is between:and: and arguments are 5 and 10"
    $d between: $a and:$r       "Receiver is character d, message selector is between:and: and arguments are characters a and r"
    3 raisedTo: 17              "Receiver 3, message selector is raisedTo: and argument is 17"
    Dialog request: 'Your name, please' initialAnswer: 'John'
				"Receiver is 'class' object Dialog (more later), selector is request:initialAnswer:"
    'aLongWord' chopTo: 5       "Receiver is string 'aLongWord', selector is chopTo:"
The following expression uses a single keyword message with six keywords. Its selector is choose:fromList:values:lines:cancel:for:
I show it using multi-line formatting commonly used for keyword messages with multiple arguments, but you can type it all on one line or in any other style because Smalltalk does not pay attention to 'white space'. Evaluate the expression by selecting all seven lines together and executing "Print it"
    Dialog
	    choose: 'Which line do you want?'
	    fromList: #('first' 'second' 'third' 'fourth')
	    values: #(1 2 3 4)
	    lines: 8
	    cancel: [#noChoice]
	    for: Dialog defaultParentWindow
Note that identifiers (names of unary messages and keywords) start with a lowercase letter and contain only letters and digits. They have unlimited length and are case sensitive, so that asUppercase and asUpperCase are different. To verify this, try evaluating:
    'abc' asUpperCase  "will trigger an error"
This is wrong because there is no message called asUpperCase (remember - its called: asUppercase), and Smalltalk will open an error dialog. You can respond with cancel (abort) to terminate execution and correct the code, or with correct it. In this case, Smalltalk will offer one or more similalrly sounding selectors and if you select one, it will correct the mistake and continue evaluation. The correction mechanism usually succeeds when you make a small typing mistake but larger mistakes must be corrected by hand.

3. Every message returns an object and messages can thus be combined

Every message returns an object and you can thus use an expression as the receiver of another message. Evaluate the following lines one by one with "Print it".

    5 factorial squared                 "Integer 120 - the result of 5 factorial
						     - is the receiver of message squared"
    'abc' asUppercase reverse           "The result of 'abc' asUppercase
					 is the receiver of message reverse"
    13 factorial sqrt truncated even    "Interpretation: Calculate 13 factorial, then its square,
							 truncate to an integer, and tell me whether it is even"
Note that you can even select and evaluate parts of the above 'chained' expressions. As an example, you can execute only the 5 factorial or the 13 factorial sqrt parts of the lines above if you wish. This is sometimes very useful.

For the same reason, expressions can also be used as arguments as in

    3 raisedTo: 5 squared                       "The result of 5 squared is the argument of the keyword message raisedTo:
						 so this is equivalent to 3 raisedTo: 25"
    'the number is: ', 5 printString            "The result of 5 printString is the argument of the binary concatenation
						 message , and equivalent to 'the number is: ', '5' "
    Dialog confirm: 'abc' asText allBold        "The argument of confirm: is the result of sending two consecutive messages to 'abc' "
The following combination where both the receiver and the arguments are calculated is also valid
    5 factorial between: 3 squared and: 3 * 5 * 9
but the following formulation is more readable and thus preferable:
    (5 factorial) between: (3 squared) and: (3 * 5 * 9)
Smalltalk programmers routinely chain messages, sometimes to even greater depth, but the facility should not be overused at the expense of readability.

Evaluation of combined messages obeys the following simple rule:

4. Messages are executed from left to right; parenthesized expressions first, unary messages next, binary next, keyword last

As an example, check that

    3 + 2 raisedTo: 2 squared + 7           "squared first (unary), + next (binary), raisedTo: last (keyword)"
is equivalent to
    (3+2) raisedTo: ((2 squared) + 7)               "Parentheses first, unary messages next, binary then, keyword last."
These simple rules apply to everything; there are no other precedences - even where you might expect them. So, for example,
    5 + 3 * 4
is a sequence of binary messages and is evaluated left-to-right. It is thus equivalent to
    (5 + 3) * 4
which is not what you might have expected. To avoid unpleasant surprises, use parentheses for more complicated expressions, even when they are not necessary. Adding them does not affect evaluation speed but improves readability.
    5 + (3 * 4)

5. Statements

Program code usually consists of more than a single expression. I will call a sequence of expressions a code fragment (some people call it a script) and its components are called statements. Consecutive Smalltalk statements are separated by periods, just as English sentences.
As an example,

    3 squared.
    5 factorial
is a small (and meaningless) code fragment consisting of two statements. If you execute it by selecting both lines and using "Print it", you will get the result returned by the last expression.
As another example, the following two lines clear the 'Transcript' - the bottom part of the main Launcher window - and display 'Hello world'
    Transcript clear.                   "Note the period separating this statement from the next."
    Transcript show: 'Hello world!'
Execute this code fragment with "Do it" and then examine the Launcher window. (You can execute it with "Print it" as well, but here we are interested in the effect of the two lines rather than the object returned by the last expression. To see the difference, try "Print it" as well.)

Notes: - The word Transcript is capitalized because it denotes a different kind of entity than the objects we used so far. I will explain this later. - Execution of "Print it" consists of "Do it" followed by the display of the result of the last message.

6. A sequence of messages to the same receiver can be cascaded

If a sequence of consecutive statements has the same receiver, you can use cascading to eliminate the need to retype the receiver.
As an example, when you execute the following fragment with "Do it"

    Transcript clear.                   "Note that this and the following statements have the same receiver Transcript."
    Transcript show: 'Hello world!'.
    Transcript cr.
    Transcript show: 'How are you?'
you will see that it has the same effect as the 'cascaded' form
    Transcript clear;                   "Note the semicolon separating the 'cascaded' messages instead of a period separating statements."
	show: 'Hello world!';           "Receiver is not repeated."
	cr;
	show: 'How are you?'
The semicolon indicates that the next message is sent to the same receiver as the current message. Note that although each consecutive message in a cascade goes to the same object, the state of that object may be changing as the messages are evaluated.
In this example, the receiver of all messages is Transcript.
The original Transcript may have contained text, but after the clear message, it is empty - its state has changed.

You can always write code without using cascading - cascading is just a shorthand to save typing and possibly make code easier to read. Some people think that code is easier to read without cascading and avoid it. I (Ivan) think that when you use proper indenting, cascading improves readability.

7. Variables

After evaluating a message, Smalltalk automatically discards the returned object unless you assign it to a variable, or unless the object is referenced by another object that still exists. (This is called automatic garbage collection.) So if you need to keep an object for later use, assign it to a variable, a named reference to an object that can be used to refer to it later in the program. All variables in Smalltalk must be 'declared' before the first statement and the declaration lists the names of all required variables but does not specify their type. Here is an example:

    | price tax total |             "Declaration: Variable names separated by spaces."

    price := (Dialog request: 'Please enter price' initialAnswer: '100') asNumber.
    tax := (Dialog request: 'Please enter tax %'  initialAnswer: '20') asNumber.
    total := price + (price * tax / 100).
    Transcript clear;
	    show: 'price: ', price printString; cr;
	    show: 'tax: ', tax printString; cr;
	    show: 'total: ', total printString; cr.
Evaluate this code fragment with "Do it" and examine the Transcript to see the result.

The reason why variables don't have a declared type is that they are just pointers to objects, essentially addresses of memory locations holding the representation of the object to which they are 'bound'. As a consequence, a variable can point to one object in one part of a fragment and another in another part of the same fragment, but this is considered poor programming style.

The variables introduced in this code fragment are called temporary variables and their scope (range in which they can be used by the program) and lifetime are limited to the code fragment. When the code fragment is fully evaluated, they cease to exist and the objects that they point to are discarded - unless they are referenced by other objects. We will see other types of variables later.

The := is the assignment operator and it binds the result of the right hand side of the assignment statement to the variable on the left hand side.

Notice that in the original ST-80 and still today in Squeak Smalltalk, the assignment operator is the '_'-character, which is displayed as a backarrow ( <- ) in those systems' fonts. Most modern Smalltalks have changed to use ':=', but still support the old syntax. (In ST/X, there is an option in the settings dialog to enable this).

Part 3: Objects and classes

1. Objects have properties

Real objects have properties: A person has a height, weight, name, address, and age, and many of these properties change during a person's lifetime.
A car has a manufacturer, color, and mileage, and different cars usually have different values of these parameters.
A major justification of object-oriented languages such as Smalltalk is that they can be used to model real-world objects and concepts.
As a result, Smalltalk objects may also have properties and their values may also change during their lifetimes.
As an example, every Borrower object representing a library patron in a library application may be characterized by a first and a last name, an ID, and a list of borrowed books, but different Borrower objects probably have different values of these properties.
Every Book object may have an author, a title, a publisher, a library number, and a borrower, but different Book objects have different property values. And every Fraction object is naturally characterized by its numerator and denominator.

To examine the properties of a Smalltalk object, use the Inspector tool.
Evaluate each of the following lines separately with "Inspect it" from the <operate> menu:

    3 @ 5                                       "Returns a Point object as indicated in the label at the top of the Inspector window.
						 Its components are shown below."
    Rectangle origin: (3 @ 5) corner: (25@30)   "Returns a Rectangle object."
    3 / 5                                       "Returns a Fraction."
The "Inspect it"-Command first executes "Do it" and then opens an inspector on the object returned by the last message. You can also inspect an object by sending it the inspect message as in
    (3 @ 5) inspect         "Evaluate with Do it"
As you might guess, the inspect message is the essence of the "Inspect it" command. (Q: what happens if you use the "Inspect it"-Command in the previous example, instead of "Do it" ?)

If an object has properties they are, of course, again objects because everything is an object. The rectangle returned by the second line is a composite object - its components are objects (points - ST/X: numbers) with their own properties. To see their structure, select the line with its name and execute "Dive" (ST/X: "Follow") in the <operate> menu of the list. To return to the previous Inspector level, execute "Back" in the <operate> menu or use the arrow button at the top of the Inspector.

2. Every object is an instance of a class

All fractions are objects are objects of the same kind and share the same definition, which says that a fraction has a numerator and a denominator and that it understands certain messages. The only difference between individual fractions is that they may have different numerator and denominator values.
Similarly all point objects share the same definition and so do all text objects. The definition of all objects of the same kind is gathered in a special kind of object called a class. Fractions are defined in class Fraction, points in class Point, and text objects in class Text. You can think of a class as a 'blueprint' kind of object mainly used to create instances of the objects that it defines. Class Fraction can create objects such as 3 / 5 and 4 / 7 and these two fractions are two instances of class Fraction.

Properties of an object are stored in its internal slots called instance variables and each instance of a class has the same own instance variables with its own private values.
As an example, every Point has an x and a y coordinate. Similarly, every Fraction has a numerator and a denominator - again each with its own private values. The value of an instance variable of a given object may or may not change during its lifetime.
As an example, when a window object is moved on the screen or resized the instance variable that defines its size and position changes its value. Objects thus carry along their private properties but their definitions are in their defining class. Note that Smalltalk class names always begin with a capital letter.
The questions related to the fact that classes are objects are related to the very interesting but advanced topic of metaprogramming. I don't cover it in this introduction.

If you ever need to find the class of and object, send it the class message as in

    3 class                 "Execute with Print it or with Inspect it."
    (3 @ 5) class
    (Rectangle origin: (3 @ 5) corner: (25@30)) class
Exercise:
Note: When an exercise requires writing and evaluating code, I recommend using a separate Workspace.
  1. What is the class of 3/5; the class of 'abc'? (Note: If you are not careful, you may get an 'exception' for 3/5 and Smalltalk will open an Exception window. If you do, close the Exception window, think about evaluation rules, correct your expression, and try again. We will deal with exceptions in more detail later.)

3. Use the Browser to view class definitions

Smalltalk's tool for viewing, editing, creating, and destroying class definitions is called the Browser. Smalltalk provides several kinds of browsers and you can open them from the Launcher or by sending them a message such as

    SmalltalkWorkbench browseClass: Fraction        "that is the VisualWorks version"
    Smalltalk          browseClass: Fraction        "that is the Smalltalk/X version"
    Smalltalk          browseInClass: Fraction      "another Smalltalk/X version"
When you execute this expression with "Do it", you will get a Browser on class Fraction.
Notice, that most Smalltalk systems offer various browsers and corresponding startup messages; for example, there might be browsers showing a single class only, multiple classes, inheritance trees etc.

A much more common and easier way to open the Browser via the Launchers menu or the corresponding Launcher button.

The Browser is a very powerful tool that allows you to see and edit all code including, for example, the code that defines the Browser itself and this Workspace. It allows you to change anything you want - even the rules of the language - at your own risk. The Browser is the tool that Smalltalk programmers use to create new applications. We will see how later.

Exercises:

  1. Open the Browser window from the Launcher and look at the definition of class Fraction. To find it, execute "Find Class" in the <operate> menu of the upper left view/pane of the Browser.
  2. Find all classes containing the word Integer. (Hint: Use the "Find Class" command with the pattern *Integer*.)

4. Understanding the Browser window

Smalltalk provides several browsers, each suitable for a particular task. We will describe the Class Browser (in the following simply the Browser) because it is the simplest and easiest to use. The other browsers have a similar structure but may have more or fewer components.

A Browser window has four list panes at the top and a text view at the bottom. The main roles and uses of these views are as follows:

Exercises:

  1. Which classes are defined in category Magnitude-Numbers?
  2. What are the categories of Rectangle, Date, String?
  3. Which instance variables are defined in class Rectangle and what is the comment of the class?
  4. Which protocols are defined on the instance side of class Rectangle? Which ones on the class side?
  5. What is the definition of method "corner:" in class Rectangle and which protocol is it in? (Hint; VW users: Use command "Find Method.." in the protocol view to find a method in the selected class if you don't know its protocol. ST/X users: either use the "Find Response to"-command in the Find menu, or select "*all*" in the protocol list to see all methods)
  6. Smalltalk developers often create class-side messages to provide examples of class uses. These examples are often gathered in the class protocol called "examples" or "documentation".
    As an example, find and evaluate the example methods in class Spline. The complete expression to evaluate the example message is given in the comment at the beginning of the method.
    (VW users: As an example, you will see that the expression evaluating example method demoFlatness is Spline demoFlatness. ST/X users: see the examples in the "examples" method on the class side.)
    Select the examples text and evaluate with "Do it".
  7. To see all classes that contain protocol examples, VW users should evaluate the following with "Print it" or "Inspect it":
        Object allSubclasses
    	select: [:aClass |
    	    aClass class organization categories includes: #examples]
    
    As the code is organized slightly differently in ST/X, the examples are found via:
        Object allSubclasses
    	select: [:aClass |
    	    aClass class implements:#examples]
    
    Use the information obtained from this expression to find and evaluate other example methods. (The expression above is an example of the use of Smalltalk's reflectivity. One of its most important uses is the implementation of tools such as the Browser, the Inspector, and the Debugger.)

    ST/X users: the browsers protocol list menu contains a function "Spawn Protocols Matching..." where a protocols name such as "examples" can be entered. This allows browsing all methods which are found in a particular protocol.

5. Class messages

Because classes are objects, they understand messages. These messages are listed on the 'class side' of the class definition and are called class messages. (Messages on the 'instance side' are called instance messages.) To view them in the Browser toggle the class/instance button which is found below the class or protocol list.

Because classes are used mainly to create instances, most class messages are instance creation messages. Almost every class inherits (to be explained) and can execute message new to create an uninitialized instance of itself with nil as the value of all its instance variables. (The nil-Object is the single instance of class UndefinedObject and represents an object that 'does not have a value'.)
As an example, evaluate the following with "Inspect it" and examine the instance variables of the returned object:

    Rectangle new   "Message new is a class message - its receiver is the Rectangle-class."
In addition to understanding new, many classes define their own specialized instance creation messages that create initialized instances.
As an example, inspect individually the following lines:
    Circle center: 23 @ 12 radius: 15       "center:radius: is a class message - its receiver is class Circle. It returns an instance of Circle."
    Time now                                "now is a class message - its receiver is class Time.
					     The result is an instance of Time in the current time zone.
					     You can change the time zone via the File -> Settings command sequence in the Launcher."
    Rectangle fromUser                      "Lets you draw a Rectangle on the screen and returns the resulting Rectangle object."
    Date today                              "Read the comment of class Date to understand the meaning of its instance variables."
How can you recognize that center:radius:, now, fromUser, and today are class messages? If you are looking at an expression, the answer is that the type of a message can be determined from the nature of its receiver. Receivers of class messages are classes and class names start with a capital letter and this gives a strong hint.
There are a few exceptions to this rule - as an example, Transcript is not a class although it begins with a capital letter. This is because capitalized names denote 'shared objects' and classes are only one of several kinds of shared objects. More on this later.

Exercise

  1. Find the definitions of the above instance creation messages in the Browser. What is the name of their protocol?

Here is an example that combines a class message with an instance message:

    Date today previous:#Monday         "today is a class message (receiver is class Date) and returns an instance of Date;
					 previous: is thus an instance message because its receiver is the object Date today, an instance of Date."
It is not uncommon for a class message and an instance message to have the same name.
As an example, the message 'initialize' is defined on both class and instance sides of several classes. This does not create any problems because the nature of the receiver determines which definition should be used - when the receiver is a class, the class-side method is evaluated, when the receiver is an instance, the instance-side definition is used.

Some class messages do not create instances but return information related to the class. As an example, use the Inspector to check the result of

    Time millisecondClockValue          "Does not return a Time object - returns a SmallInteger."
    Window platformName                 "Does not return a Window object - returns a String."
    Filename volumes                    "Does not return a Filename object - returns a collection of strings."
Still other class messages initialize the class itself or perform various other functions such as execute examples.

Note that although most objects are created by sending an instance creation message to a class, some of the most common objects can be created as 'literal' objects without sending a creation message. Several examples are

    32              "An integer number literal."
    41.3            "A floating-point number literal."
    'abc'           "A String literal."
    $x              "A Character literal - letter x."
    true            "The single instance of class True."
    false           "The single instance of class False."
    nil             "The single instance of class UndefinedObject."
Think of literals as being objects which are created by the compiler.

Exercise:

  1. Many useful class messages are defined in class Dialog. Browse the definitions and execute the example code enclosed in comment brackets at the beginning of each of them with "Print it".
    ST/X users: Notice, that Dialog is an alias to a class named DialogBox. Find usage examples in comments at the end of the methods and in the classes examples method.

6. Class hierarchy - inheritance

Smalltalk classes are related - every class (except one, as you will see) has exactly one superclass and inherits its class and instance methods and variables. In other words, a class has all instance variables of its superclass (plus its own) and understands all its messages (plus its own).
As an example, the definition of class Fraction extends the definition of its superclass - class Number. Fraction thus understands all methods defined in Number (plus its own) and its instances have all its instance variables (plus its own). (In fact, class Number does not have any instance variables so Fraction does not inherit any.) When you draw a diagram of classes and their superclasses in the form of a family tree, you will get Smalltalk's class tree. The class on the top is class Object and this class is the only one that does not have a superclass.

If A is the superclass of B, and B is the superclass of C, C inherits from B, including everything that B inherited from A. C thus inherits everything from B and A. Inheritance is thus transitive and all classes ultimately inherit all behavior defined in class Object. As a consequence, if you want to define a message that every object should understand, define it in class Object. Modifying 'base clases' such as Object is not very common but it is possible.

To find the superclass of a class and its complete class hierarchy with all superclasses and subclasses, select the class in the Browser and choose "Hierarchy" in the View command ("ST/X: Class Hierarchy" in the View menu). VisualWorks Note: you must not select a method protocol if you want to see the hierarchy.

You can also ask a class what is its superclass as in

    Fraction superclass
Message superclass is another example of Smalltalk's reflectivity, and the reason why the Browser can display the class hierarchy.

Exercises:

  1. What are all the superclasses of SmallInteger, Date, Rectangle, Object?
  2. Message subclasses returns the collection of all immediate subclasses of the receiver, message allSubclasses returns the collection of all subclasses of the receiver down the class tree. Test these messages on class Number. (Note: These and related methods are defined in class Behavior.)
  3. Find all subclasses of SmallInteger, Date, Rectangle, Object.
  4. Evaluate Object allSubclasses size to find how many subclasses class Object has. The result depends on how many parcels (packages) you loaded and how many classes you added or deleted.

7. Classes can override inherited methods

An ostrich is a bird but it does not fly. In other words, ostrich redefines one of the behaviors that it inherits from its 'bird superclass'. A similar feat can be achieved in Smalltalk by overriding an inherited method definition by simply writing a new definition of the method in the class that should execute the message differently. Any class may redefine any of its inherited methods but cannot remove any inherited method or any of its inherited instance variables.

As an example of overriding, class Fraction inherits the definition of = but redefines it because equality has a special meaning for fractions. (Two fractions are equal if they satisfy a certain arithmetic formula relating their numerators and denominators.) This definition overrides the inherited definition and when you ask a fraction whether it is equal to another fraction, it executes its own definition of equality rather than the inherited one. This is because when you send a message to an object, its evaluation starts with a search for its definition in its class. If the class contains the definition, the definition is evaluated, otherwise the search for the definition continues in the superclass, and so on, until a definition is found and executed, or the top of the hierarchy is reached and the definition is not found.
If the definition of the method is not found, Smalltalk sends message doesNotUnderstand: to the original receiver, and this method by default creates an 'exception' object that indicates that (by default) opens an Exception window. (The default behaviors can be overridden.)
To see an example of an exception created when a message sent to an object is not found in the path from the class of the receiver to the top of the hierarchy in class Object, evaluate

    13 asUppercase
with "Do it". You will get an Exception window because neither the class of the receiver (SmallInteger) nor any of its superclasses understand asUppercase, which is defined in class Character on a branch from Object that does not lead to integers. Click "Terminate" (VisualWorks) (ST/X: "Abort") or in the Exception window to abort the evaluation. We will see later how you can handle exceptions programmatically.

Exercises:

  1. Which class defines the = method that Fraction inherits?
  2. Find all implementers of methods asUppercase, fromUser, <, and printOn: using the "Browse -> Implementors of..." command in the Launcher. Which of these definitions override inherited methods?
  3. When Smalltalk programmers want to make it impossible for an object to execute an inherited message, they define its body as shouldNotImplement. Use the Launcher to find all references to this message to see how this is done. Try what happens when you send a 'forbidden' message as in
        True new    "Class True 'forbids' message new"
    

8. Abstract classes

Inheritance is great for avoding duplication of code. (Duplication is bad not only because it means extra work, but because it is dangerous - if you change something and forget to change even one of the copies, your application may fail to work.) As an example, if you need classes to define classes SavingAccount, CheckingAccount, and SpecialSavingAccount, many of the methods (such as deposit and withdraw) and many of the instance varibales (such as accountNumber, owner, balance) are probably needed in all three classes. To avoid this duplication, define Account as a superclass of these three classes, and include all the shared instance variables and methods in it. The three subclasses will not have to redefine them, unless some of the methods have different behavior.

Our application will never instantiate Account it does not use the general concept of an Account; it assumes only instances of the three subclasses. This is why Account is called an abstract class whereas SavingAccount, CheckingAccount, and SpecialSavingAccount are called concerete - they are designed to be instantiated. Smalltalks class hierarchy contains many abstract superclasses and class Object at the top of the hierarchy is itself abstract.

Note that there is nothing in Smalltalk that marks a class as abstract - the distinction is only a design feature in the mind of the designer and there is nothing to prevent the user from treating it as concrete.
As an example, inspect

    Object new
It does create an instance even though you would very rarely want to do that.
(But: Yes, there are real uses for this; for example, as a unique token.)

Some abstract classes take precautions against creating instances as a protection from misuse. As an example, class ArithmeticValue redefines new as

    new
	"Numbers should be created only through arithmetic operations."
	^self shouldNotImplement        "The special word self refers to the receiver of the message,
					 in this case class ArithmeticValue or one of its subclasses."
Don't attempt to evaluate this code, it's not a code fragment but a method definition. As a consequence of this definition, executing
    ArithmeticValue new
produces an exception (Notice: Not in ST/X).
Since ArithmeticValue is the superclass of all number classes, they all inherit this definition and refuse to execute the new message.
As an example,
    Fraction new
also opens an Exception window (Also: Not in ST/X).

One of the typical features of abstract classes is that they contain methods that are redefined in all their subclasses. These methods are defined essentially as templates, markers that serve as reminders that the concrete subclass must define them.
As an example, all numbers are supposed to understand multiplication, but most number classes implement it in a special way.
Class ArithmeticValue thus 'implements' multiplication as

    * aNumber
	^self subclassResponsibility
If you defined a new subclass of ArithmeticValue, forgot to define message *, and tried to multiply your new number, you would get an Exception window telling you that the class was supposed to implement this message but didn't. This is the result of message subclassResponsibility.

Exercises

  1. Find some references to (i.e. senders of) subclassResponsibility and explain why they are needed.

Part 4. Creating and editing methods and class definitions

1. Defining a new method or editing an existing one

Smalltalk programs consist of classes with methods and this part shows you how to create and edit them. This section shows how to create a method. Because we start with methods, my example will use an existing class and add a new method to it or modify an existing method.

To define a new imethod

To modify an existing method, use the same procedure but edit the existing text instead of retyping it.

As an example, assume that we want to define an instance method to calculate the cube of a number.
The method will be used as in

    15 cubed
or
    1.5 cubed
Clearly, the message should be understood by all types of numbers, just like the squeared-method.
The logical class to put it in is thus where the squared method is defined, which is instance protocol mathematical functions in class ArithmeticValue.
The definition (explained below) will be as follows:
    cubed
	^ self * self * self
Type (or copy and past) the text into the code view in the ArithmeticValue class and "mathematical functions" protocol on the instance side and click "Accept" in the <operate> menu. If you have not made any mistakes, this will compile the code and the protocol list will now display the selector cubed and the method will be added to the library.
To make sure that the new method is saved in the 'image file' when you quit Smalltalk, use "File -> Save" in the launcher either now or later, or save on exit. If you exit Smalltalk without saving, all work done since the last save is partially lost because the image file doesn't change automatically. The 'changes file' (accessed via the ChangesBrowsr) allows you to recover the code, but the procedure is a bit more complicated - consult the On-line Help for details.

Test that the method works, for example by evaluating

    3 cubed
    -3 cubed
and
    (3.55) cubed
Notes:

Exercises:

  1. Define a unary method called inc that returns its receiver incremented by 1. As an example, 34 inc returns 35.
  2. Define a unary method isPalindrome that returns true if its receiver string spells the same in reverse. As an example, 'aba' isPalindrome should return true, but 'abs' isPalindrome should return false. (Hints: Use method reverse (ST/X: reversed) and don't forget the return operator.)
  3. Define a binary method "+*" that returns the sum of the receiver and the argument, all multiplied by the argument. As an example "3 +* 5" returns "(3 + 5) * 5" or "40".
  4. Define a keyword method smallerThan:orGreaterThan: that returns true or false under obvious circumstances. (Hint: This method is the logical negation of message between:and:, and the logical negation message is not.)
  5. Define a keyword method implies: that returns true for any combination of receiver true and false, except when the receiver is true and the argument is false. As an example true inplies: true returns true, but true implies: false returns false. (Hints: Write a subclassResponsibility definition in class Boolean, and override it in class False and in class True. Don't forget the return operator.)

2. Debugging

The method that you defined or a code fragment that you wrote may not work the first time. If your error is that you are sending a message to the wrong object, for example asUppercase to a number as in

    3 asUppercase   "Try it"
Smalltalk opens an Exception window that can be used to open the Debugger to see what is wrong. You can then correct the code and accept it in the Debugger as you would in the Browser (using the "Accept" command), possibly edit the data (two inspectors at the bottom of the Debugger), and continue execution. Or you can "terminate" (ST/X: abort) execution and start all over.

If the program executes but the result is not as expected, the error is in the logic of the code. The first thing that you might want to do is to read your code and see if you can correct it. If you don't see the problem but think that you know its approximate location, or a place where you might start searching for the problem, you can insert a breakpoint (usually a self halt statement). When execution reaches this point, Smalltalk will open an Exception window and then the Debugger. (halt is defined in Object and you can thus send it to any receiver such as self halt, nil halt or 3 halt.)
You can then continue executing the code step by step and find and correct the problem.

As an example of the use of a breakpoint, evaluate the following faulty program and correct the mistake in the Debugger.

    | price tax total |

    price := (Dialog request: 'Please enter price' initialAnswer: '100') asNumber.
    tax := (Dialog request: 'Please enter price %'  initialAnswer: '10') asNumber.

    "Insert self halt here to open an exception window."

    total := price + (price * tax / 10).
    Transcript clear;
	show: 'price: ', price printString; cr;
	show: 'tax: ', tax printString; cr;
	show: 'total: ', total printString
When you add a breakpoint at the indicated point, Smalltalk will open an Exception window saying 'Halt encountered'. Open the Debugger with "Debug", select 'unbound method' in the list at the top (it refers to the code from this workspace), and continue executing it using either "Step" (to walk over the next message) or "Send" (to enter the definition of the next message). When you find the problem, correct it directly in the Debugger and "Accept" the change.

The inspectors at the bottom of the Debugger window show the instance variables of the receiver (left) and temporary variables and message arguments (right), and you can change their values by selecting the variable, entering a new value, and accepting it with "Accept". You can then continue executing the code in the Debugger making any corrections you want, or exit from the Debugger and proceed with execution (command "Proceed" in the <operate> menu in the stack view at the top of the Debugger). You can also terminate execution and return to the Workspace or the Browser. When you have corrected the mistake, remove the breakpoints.

Smalltalk programmers depend on the Debugger so much that some actually develop code with it.

3. Namespaces

To be able to create classes, you must first understand the concept of a namespace. As you already know, classes are grouped into categories. In a similar but completely independent perspective, classes are also collected in namespaces. Whereas a category is just an organizing principle with no effect on run-time behavior, namespaces have profound effect on execution.

To understand why some Smalltalks have namespaces, consider a familiar analogy. My office phone number is 585-1467. If you call me from within my area, that's all you need. However, if you are in Toronto or in San Francisco, this number is either unassigned or belongs to another person and to reach me, you must put my area prefix 902 in front of the seven-digit code. And if you want to call me from Europe, you must put even more digits in front of that to select North America. The 'area code' prefix of a phone number thus partitions phone numbers into unambiguous sets - one for Ontario, one for Montana, etc. - and allows the basic seven-digit codes to be assigned to many different people across North America with no confusion.
The idea of namespaces implements exactly the same principle for Smalltalk classes.

A namespace is simply a way to get around the serious limitation of earlier versions of Smalltalk that required that every class have a unique name. This meant that code from different sources could only be combined if the sources did not define clases with the same class. If I wrote a library catalog program with a class called Book and loaded the On-line Help parcel (package), my Book class could clash with the Book class that Smalltalk uses in its On-line Help.
In fact, one of the Book classes would overwrite and destroy the other and one application would stop working.

Namespaces partition class names and make it possible to name classes in one namespace without any regard for class names in other namespaces. Within one namespace, class names must be unique, but two different namespaces may contain classes with identical names without any conflict. As a consequence, if I put my Book class into my own namespace, Smalltalk doesn't mind that there is another Book class in the another namespace and will not confuse one for the other because the other class is invisible to my namespace - unless I 'import' the other namespace into it.

Namespaces are organized in a tree-like structure with namespace Root at the very top, the namespace Smalltalk underneath, and other namespaces below Smalltalk. (ST/X: no Root here; Smalltalk is a TOP namespace, among others)
To see all namespaces now in your image, open the System Browser from the Launcher and select its NameSpaces mode of display. The structure of the namespace tree is relevant mainly for accessing a name in a different namespace and for defining your own namespaces: Should you need to access class Y in namespace X contained in namespace Smalltalk, you can use the dot notation, as in: Smalltalk.X.Y
(ST/X: two colons instead of a dot, as in Smalltalk::X::Y; or enable dot notation via a compiler switch).
(The mechanism is designed so that you can leave the root namespace(s) out of these expressions.)
In VisualWorks, you can import the required namespace or class by specifying it in the import: argument of the namespace definition message in the browser.
(In ST/X, there is no import path; namespaces always only import the global Smalltalk namespace)

As you see, the concept of namespaces is very important, but I am not going to explore it further except for giving a simple example of class creation, and refer you to On-line Help for details.

Exercises:

  1. Use the System Browser to draw the whole namespace hierarchy of your class library. Note that its elements and depend on the parcels (packages)that you loaded because parcels may add new workspaces.
  2. Namespaces form a namespace tree in the same way that classes form a class tree. Is the class concept of inheritance valid in the namespace tree? (Hint: See On-line Help or documentation.)
    (In ST/X, the recommendation is to NOT use nested namespaces - it was found to create more confusion.)

4. Defining a new class

To define a new class, you must know into which namespace and category to put it. If the namespace does not yet exist, you can create it using the System Browser. The next decision is to select the superclass of the new class. Because a subclass specializes (extends) the properties and functionality of its superclass, the superclass should be a class that serves a more general purpose than the new class. As an example, Vehicle would be a reasonable superclass for Car if your application deals with several kinds of vehicles and each of them requires its own class, and Account is a good superclass of SavingsAccount if you have several kinds of accounts. If you can't think of a suitable existing superclass , make your class a subclass of Object.

After deciding these basics, you must decide on instance variables and write and "Accept" the definition of the class in a browser. The next step should be adding a comment to the class. Command View -> Comment from the Browser's menu bar displays a comment template, edit it as appropriate and "Accept". Finally, create the necessary instance and class protocols and define methods. We will now demonstrate the procedure on a simple example.

Example (VisualWorks specific):
Our goal is to create class Name with superclass Object in the Smalltalk.Test namespace (ST/X: Smalltalk::Test), and category "Examples".
The class will have instance variables firstName (a String), middleName (a Character), and lastName (a String). Its protocols will include instance creation and instance variable accessing. Our class is a simple data holder class, not a very good example from the point of view of class design, but almost the simplest example of class definition.

There is no namespace called Test within namespace Smalltalk so you must create it. Open a System Browser, select namespace Smalltalk, and execute commands "Add" (from the menu bar) and then "Namespace". This displays the template

    Smalltalk defineNameSpace: #NameOfPool
	private: false
	imports: '
			OtherNameSpace.*
			private Smalltalk.*
			'
	category: 'As yet unclassified'
Edit it as follows, noting that I deleted the underlined word above:
    Smalltalk defineNameSpace: #Tests
	private: false
	imports: '
			private Smalltalk.*
			'
	category: 'As yet unclassified'
The imports: keyword gives our namespace access to all classes in namespace Smalltalk which, in turn, provides access to other very important namespaces.

We can now create the new class in the new namespace. To do this, execute commands "Class -> Add class" from the menu bar or from the <operate> menu of the second list view of the System Browser, and select fixed size. This will display the template

    Smalltalk.Test defineClass: #NameOfClass
	superclass: #{NameOfSuperclass}
	indexedType: #none
	private: false
	instanceVariableNames: 'instVarName1 instVarName2'
	classInstanceVariableNames: ''
	imports: ''
	category: 'As yet unclassified'
I again underlined the parts that you must edit. Note that the text is, in fact, a message to your selected namespace - you can read it as a command to the namespace Smalltalk.Test to create a new class called Name. (Because it is an ordinary message, you could use it even inside a program, thus creating a new class from a running program. The same, of course, holds for the creation of a namespace.)
Edit the text by modifying the class name and instance variables as follows:
    Smalltalk.Tests defineClass: #Name
	superclass: #{Core.Object}
	indexedType: #none
	private: false
	instanceVariableNames: 'firstName middleInitial lastName '
	classInstanceVariableNames: ''
	imports: ''
	category: 'Examples'
and click "Accept" from the <operate> menu. malltalk adds the Core. part in front of Object, creates the class, adds it to the library, and displays it in the Browser.

The next step is to write the class comment. With the new class selected, select Comment in the menu bar View command and edit the template as follows:

Instances of this class represent simple person names.

    Instance Variables:
	firstName               <String>
	middleInitial           <Character>
	lastName                <String>
The comment is, of course, only for documentation and has no run-time effect. It is, however, poor programming practice to neglect it because comments are very useful for understanding an unfamiliar class.

It is now time to write methods. I will start with instance creation because I want to test everything as soon as possible and I can't test any instance methods without reasonable instances. I could, of course, create instances with the new message and then assign values to instance variables by a sequence of cascaded 'setter messages' as in

    Tests.Name new          "ST/X: Tests::Name"
	firstName: 'John';
	middleInitial: $C;
	lastName: 'LeCarre'
asssuming that I have already defined firstName:, middleInitial:, and lastName:. However, I will assume that when the application runs, it gets name properties from a dialog window and so a Name is always created with known components. Given this background, I decided on the following instance creation pattern:
    Tests.Name              "ST/X: Tests::Name"
	firstName: 'John'
	middleInitial: $C
	lastName: 'LeCarre'
The new message firstName:middleInitial:lastName: is a class message because its receiver is class Name. What should the message do? It must create a new instance of Name, send it an instance message to initialize the instance variables, and return the resulting initialized object. The definition is thus
    firstName: string1 middleInitial: aCharacter lastName: string2
	^ self new
		firstName: string1      "Instance message - to be written."
		middleInitial: aCharacter
		lastName: string2
Note the naming of arguments:
Smalltalk programmers prefer to use names that suggest the class of the argument (such as aCharacter - indicating an instance of Character) because the keywords clearly identify its role. This style helps the future user of the method to use proper arguments when sending the message.

To define the method, create a new class-side protocol instance creation and define this message in this protocol by typing (or pasting) it over the method template text. There is no need for a method comment because the purpose and logic of the method are obvious. Click "Format" and "Accept" and the method appears in the protocol list. If you now tried to evaluate the Name-creation expression above, you would get an exception because you have not defined the instance message used inside our definition (try it). To define the instance-side method, go to the instance side of the Browser. Method firstName:middleInitial:lastName: simply assigns argument values to the instance variables and its definition is

    firstName: string1 middleInitial: aCharacter lastName: string2
	firstName := string1.
	middleInitial := aCharacter.
	lastName := string2
Put this code into instance-side initialize-release protocol, click "Format" and "Accept", and test by evaluating
    Tests.Name              "ST/X: Tests::Name"
	firstName: 'John'
	middleInitial: $C
	lastName: 'LeCarre'
with "Inspect it" to check that the new methods produce the expected result. ("Print it" does not produce anything interesting until I redefine the operation of printString by defining a new printOn: method. Try it.)

Exercises:

  1. Extend class Name by adding 'getters' such as firstName (returns the value of firstName). Test them by creating a Name object and using the getters to print a description of the object in the Transcript.
  2. Write a printOn: aStream message to make it possible to get Name descriptions such as
    "a Name first name: 'John' middle name: $C last name: 'LeCarre'" with printString.
    Test it by evaluating the above instance creation expression with "Print it". (Hint: Look at the definition of other printOn: methods, for example in class Rectangle, and follow the same style. This kind of 'black box' reuse of existing code is common.))
  3. Create class Book in namespace Tests and category "Examples". Book will have instance variables title (a String), author (a Name), and year (an Integer). It will have an initializing instance creation message and getters for all instance variables.

5. Shared and class instance variables

To complete this overview of foundations, I will now explain two new types of variables, less common in simple applications. They are class instance variables and shared variables (ST/X: class variables).

Because classes are objects, they can have their own instance variables and although they are not really special, they have a special name. They are called class instance variables. There is really nothing unusual about them and they follow all rules of instance variables. In particular, if a class defines its class instance variables, they are inherited by its subclasses, and each of these subclasses has its own private values independent of other classes. As an example, if class Animal defines a class instance variable called Sound, then its subclasses Dog and Cat inherit Sound, but its value in Dog may be 'bark', while the value in Cat may be 'meow'. As this example shows, class instance variables are used mainly for various constants. Note that class instances cannot access them directly just as a class cannot directly access instance variables of their instances; in both cases, access requires accessor methods. Class instance variables are relatively rare.

It is sometimes useful to create objects that can be shared by several otherwise unrelated classes. As an example, many classes use various 'text constants' such as the ASCII code of the backspace character, the italic style of text, or the 'centered paragraph' style of paragraphs. None of the variables discussed so far allows this because their scope is limited. This is why VisualWorks has shared variables. These can be defined in a class (see the shared variables button in a Browser) and accessed by the class and its subclasses or their instances, or in a separate namespace. Two examples of class-based shared variables are Pi and RadiansPerDegree, both defined in classes Double and Float. An example of the second is namespace Graphics.TextConstants, which includes numerous constants divided into several categories such as Characters and Emphases. Shared variables are relatively common and they replace the concepts of pools and class variables (not to be confused with class instance variables) used in earlier versions of Smalltalk.

Exercises:

  1. Class SmallInteger has class instance variables. Examine their definition, values, and accessors.
  2. Examine 20 randomly selected class definitions and count how many of them include class instance variables.
  3. Studt the Graphics.TextConstants namespace, its categories, and some of its shared variables. Browse references to shared variable CR and explain why only a shared variable can provide this scope of access.

This completes the introduction to Smalltalk principles. In the next part, I will introduce the essential Smalltalk classes and explain some additional principles.

Part 5: Essential classes and methods

Although learning Smalltalk certainly does not require knowing all classes in the library, you cannot write Smalltalk code without knowing about the fundamental classes such as those implementing numbers and strings; this is the subject of this part of the Introduction. Additional important classes are described later in this tutorial. Once you start using Smalltalk, you will quickly learn the other classes that you need most often.

1. Class Object

Because Object is the superclass of all classes, all classes inherit all its methods. The most common ones and their protocols are listed and explained below.

printing

comparing for equality and equivalence (identity)

To see the difference between equality and equivalence, consider two Book objects defined to be equal when they have the same author and title. Two books with the same author and title but a different publisher will then be equal but not the identical book.
As another example, evaluate
    | p1 p2 |
    p1 := (Point x: 10 y: 20).
    p2 := (Point x: 10 y: 20).
    Transcript clear; show: (p1 = p2) printString.  "true - two points whose x and y are equal because Point defines = that way."
    Transcript cr; show: (p1 == p2) printString     "false - these are two equal but distinct objects."
For many objects, = and == give the same result, because Object defines = to be the same as == and many classes don't override this definition. But don't take = and == for granted because sometimes they don't give intuitively obvious results.
As an example
'abc' = 'abc'       "true"
'abc' == 'abc'      "false"
Be especially careful with numbers:
123 = 123.0       "true"
123 == 123.0      "false"
and especially:
123 = (122 + 1)   "true"
123 = (122 + 1.0)   "true"
but:
123 == (122 + 1)   "true"
123 == (122 + 1.0) "false"
In the above examples, the differences lies in the number-classes' definition of = as "having the same value". Therefore, the integer "1" compares equal to the rational number "1.0", as they have the same value. However, they are not the identical object: one is an Integer, the other is a real number.

The situation is even more confusing as some numeric classes share instances while others do not; for example, in the addition examples above, the result of "1+1" is identical to "2", as integer numbers are identically reused (the add-operation returns an already existing instance of a number which represents the integral value "2"), whereas the rational number arithmetic creates new instanes for the result.

It is highly recomended, that you use the equal operator = and NOT the identity operator == for numerics.

Finally,

copying

copying methods make copies of the receiver and create a new object of the same kind as the receiver. The exact relation between the receiver and the copy depends on how certain methods are defined ('copying semantics') as I will show next.

The two main methods in this protocol are copy and postCopy.

We will see several other Object methods later. As a rule, whenever I say that you can use a message with any object, the implication is that its definition is in class Object. Three examples of such messages universally understood messages that we have already encountered are class, halt, and inspect.

Exercises:

  1. Read the definition of method "=" in class Object.
  2. Find all classes that reimplement "=". (Hint: Use command "Implementors Of..." in the launchers menu or in the browser.)
  3. An interesting method defined in Object is "isMemberOf:". It takes a class as its argument and returns true if the receiver is an instance of the argument class. Try "3/5 isMemberOf: Number", or "3/5 isMemberOf: Fraction" and comment on the result.
  4. Message "isKindOf:" is related to "isMemberOf:". Read its definition in the Browser and try "3/5 isKindOf: Number", "3/5 isKindOf: Fraction". Explain the commonalities and differences between the two messages.
  5. Message "respondsTo: aSymbol" answers true if the receiver understands the message whose selector is aSymbol. Try "3 respondsTo: #squared", "3 respondsTo: #asUppercase", "3 respondsTo: #blaBlaBla", and "'abc' respondsTo: #+". Find at least three references to this message in the library.
  6. Read the definition of copy and postCopy in Object. Then read the definition of postCopy in Rectangle (ST/X: in Text) and explain its effect.
  7. Find the definition of method browse and give an example of its use.
  8. To see how many instance methods are defined in class Object, evaluate "Object selectors size" with "Print it".

2. Number classes

Smalltalk number classes include integers, floating-point numbers, fractions, fixed-point numbers (fixed number of decimal digits), complex numbers (extension of the basic library in a parcel), metanumbers (parcel including infinity and other unusual but useful kinds of numbers), and others.

Numbers define the obvious protocols for arithmetic and mathematical functions. Check them out, for example, in class ArithmeticValue and try the following examples with "Print it", carefully considering the effect of evaluation rules on the order of calculation:

    3 + 7 / 3                       "A message from the arithmetic protocol."
    (3 + 7 / 3) asFloat             "asFloat is in the converting protocol."
    (3 + 7 / 3) asFixedPoint: 2     "asFixedPoint: is also in the converting protocol."
    Float pi asRational             "Also in the converting protocol."
    15 log                          "log is in mathematical functions."
    0.3 sin                         "sin is in mathematical functions."
    1000 factorial                  "Protocol factorization and divisibility."
    37 raisedTo: 22                 "raisedTo: is in mathematical functions."
    16rF8 + 2r00001000              "constant can be input in hex, binary and other bases."
Some of the interesting aspects of number classes include the fact that all numbers are instances of classes (unlike most other languages where they are special data types and thus subject to different rules than objects), that they perform automatic conversions between 'large' and 'small' integers, and that you can extend their protocols - because they are objects (you did define the cubed method in an earlier exercise).

Exercises:

  1. What are the classes of the results of the above expressions?
  2. What do you consider to be the three most important instance protocols of number classes?
  3. What are the differences between Float, Double, Fraction, and FixedPoint ?
  4. What are some possible uses of FixedPoint ? (Hint: Read the class comment.)
  5. Create an instance of FixedPoint with two decimal digits. (Hint: See the class comment.)
  6. What do you consider the most useful class message in class Float ? How is its implementation in Double different?
  7. Are there any senders of message "factorial" ? What about the other messages in the same protocol?
  8. Define methods calculating the following in the appropriate number classes: Fibonacci numbers, radius and angle for complex numbers, testing for perfect square, testing for primality.

3. Strings, characters, symbols, text, dialogs

Strings

A string is an indexed collection of characters and as such it understands numerous messages for concatenation, substring insertion, searching, and other useful operations. Most of the methods commonly used with strings are defined in String and its superclass CharacterArray, but many are inherited from the collection superclasses of String. Class String itself is an abstract class and factors out shared behaviors of several different implementations of strings.
(That is VisualWorks specific and might not true for other Smalltalk implementations)

As an example of string messages, evaluate each of the following lines with "Print it":

    'abc' < 'xyz'
    'abcdefg' findString: 'de' startingAt: 1    "Returns the index or 0 if not found"
						"Elements of String and other indexed collections begin at index 1."
    'abcdefg' size
    'abcdefg' copyFrom:2 to:4
    'abcdefg' matchesRegex:'ab.*fg'
To see the nature of string objects, evaluate the following with "Do it" and examine its numbered 'indexed elements'.
    'abc' inspect           "VW users: What is the class of this string?"
Exercises:
  1. List three useful string processing messages defined in the superclasses of CharacterArray and write an expression using each of them.
  2. Write an expression to convert 'a big bear' to 'a big black bear'. (Hint: Check protocol 'accessing' in String.)
  3. A String is often created as a literal (such as 'abc'), or with the new: message as in "String new: 16". This creates a String with no characters in it but room for 16 characters. Inspect this object and note its class and explain.

Note: most smalltalk systems support multibyte strings (TwoByteString, UnicodeString etc.) to represent characters outside the 8-bit ascii character set.

Characters

Individual elements of strings are instances of class Character. Characters are usually created as literals as in ("Inspect it"):

    $a
    $3

or by a class-side instance creation message such as

    Character cr
    Character esc

You can also create a character by conversion from its numeric code as in

    Character value:8       "Character from its ASCII code."
    80 asCharacter          "Character from its ASCII code."

or extract them from strings, as in:

    'hello' at:2            "The 2nd element in the string"

Class Character is a subclass of the abstract class Magnitude (as are Date, Time and other 'comparable' objects, in particular all number classes) and as such its instances can be compared as in

    $a < $d

Notice, that a characters code is not limited to the 0..255 range of ASCII; most Smalltalk implementations support (at least) 2-byte unicode, as in:

    Character value:16r10FF

Exercises:

  1. Explore character creation messages.
  2. Explore the testing, converting, and comparing protocols of class Character.
  3. Class Magnitude is an abstract class gathering all behaviors meaningful for comparable objects. All its methods are derived from a few methods left as subclass responsibility. What are these three methods and how are the other methods derived from them? Note that due to inheritance, subclasses of Magnitude only have to define these methods and inherit all the other functionality, thus saving many redefinitions and enforcing consistency.
  4. Select one of the methods that are fully defined in Magnitude and use it with strings, dates, time objects, and numbers (all instances of Magnitude subclasses).
  5. Define method asString that returns a String with the receiver character as its only element. As an example, "$a asString" should return 'a'. Hint: use the with: instance creation message.
  6. As an exercise using class Date, define method dayToday such that "Date dayToday" returns the name of the day today as a String. As an example, "Date dayToday" might return 'Monday'.
  7. Define method hourNow such that "Time hourNow" returns the integer hour of time now. As an example, "Time hourNow" might return 7 or 15 for a.m. and p.m. time respectively.

Symbols

Symbols (instances of class Symbol, a subclass of String) are pretty much like strings, but their instances are unique whereas strings are not.
This means, that no two different symbols with the same (character-) contents can exist. Or, in other words, whenever Smalltalk is asked to create a symbol with a particular contents, it first looks for an already existing symbol with that contents and return that existing object.
Literal symbols are typed like strings, with a preceeding #-character. For alphanumeric symbols (without spaces or special characters) and for the binary-selectors, the single quotes can also be ommitted.
Therefore,
    #'abc'
and
    #abc
represent the same (identical) object.
Programmatically, symbols can be created by sending the asSymbol message to a string.

To see the difference, between symbols and strings, try

    'abc' = 'abc'
    'abc' == 'abc'      "These are two different String objects that happen to have the same value."

and

    #'abc' = #'abc'
    #'abc' == #'abc'    "Because symbols are unique, this returns true."

Some messages require Symbols as arguments, usually when the arguments are names of methods or classes. Because of this, its system primitives protocol includes selector-related messages such as

    #+ isKeyword                    "Tests whether + is a keyword message."
    #'between:and:' isKeyword       "Tests whether #'between:and:' is a keyword message."
    #'between:and:' keywords        "Extracts keyword components from selector between:and:"

and others. An interesting message that requires a Symbol as its argument is perform: and its relatives. This message is defined in Object and thus understood by all objects and it tells its receiver to evaluate the Symbol argument as a message.
As an example,

    3 perform: #factorial

tells 3 to evaluate factorial; it is thus equivalent to

    3 factorial

A typical use of perform: is to execute a message associated with a button in a window: The programmer can associate any message with a button (by naming it as a Symbol), and when the button is clicked, the action message is 'performed'.

A nice demonstration of perform: is:

    | opNameString operation |

    opNameString := Dialog request:'Perform which operation ?'.
    operation := opNameString asSymbol.
    10 perform: operation with: 5.
Try this and enter the names of various binary selectors, such as '+', '-', '*' or even 'raisedTo:'etc.

Exercises:

  1. Symbols are related to strings. Is it possible to convert one to the other?
  2. Message isInfix checks whether a selector is a binary selector. Try this and other messages from protocol system primitives.
  3. Evaluate expressions "3 perform: #'+' with: 5" and "3 perform: #'between:and:' with: 7 with: 15". Find the protocol defining perform: methods and comment on it.
  4. What happens if you modify a symbol with the at:put: message ? Why must this be as it is ?

Dialogs

Class Dialog contains many useful messages for obtaining data (often strings) from the user. All are class messages. Try the following with "Print it".

    Dialog request: 'What is your age?'                         "Returns a string - no arithmetic possible!"
    Dialog request: 'What is your age?' initialAnswer: '20'     "Returns a string - no arithmetic possible!"
    (Dialog request: 'What is your age?' initialAnswer: '20') asNumber      "Returns a number - can do arithmetic."
    Dialog                                                          "Returns selection given by the values: argument"
		choose: 'Which one do you want?'
		fromList: #('first' 'second' 'third' 'fourth')      "Prompt labels."
		values: #(1 2 3 4)                                  "Corresponding return values."
		lines: 8                                            "Number of lines displayed."
		cancel: [#noChoice]                                 "Value returned when user clicks Cancel."
    Dialog confirm: 'Are you sure you want to delete the file?'     "Returns true or false"
    Dialog warn: 'This is a warning'                                "Returns nil - the UndefinedObject"
    Dialog information: 'This is some information'                  "Returns nil - the UndefinedObject"

Exercises

  1. Message choose:... is in the 'multiple choice dialog' protocol of Dialog and uses a list widget. Find and test two other messages defined in this protocol that use buttons. What are the advantages and disadvantages of each of these messages?
  2. Examine dialog messages defined in class SimpleDialog.

Text and ComposedTextView

A String is a relatively primitive objects - a sequence of characters with no 'rendering' information (font, color, size, etc.). Consequently, a String prints itself in the default font and default color. If you want to control the rendering of a piece of text, you must convert the string to a Text object and specify the emphasis of its characters.
As an example,

    ComposedTextView open: 'abc' asText allBold asValue

opens a window, with the specified text in bold.
To display the text italicized, you can use

    ComposedTextView open: ('abc' asText emphasizeAllWith: #italic) asValue

You can also emphasize individual characters as in

    ComposedTextView open: ('abcd' asText emphasizeFrom: 1 to: 2 with: #underline) asValue

To combine several emphases, you must use an array (to be explained later) of emphasis values as in

    | emphasis |
    emphasis := #(#underline #italic).
    ComposedTextView open: ('abcd' asText emphasizeFrom: 1 to: 2 with: emphasis) asValue

The variable emphasis used above is not really necessary and I used it to make the code more readable. If you want to use color, you must specify it using an Association (created with the -> message and to be introduced shortly) as in

    | emphasis |
    emphasis := #color -> ColorValue red.
    ComposedTextView open: ('abcd' asText emphasizeFrom: 1 to: 2 with: emphasis) asValue

Exercises:

  1. Examine some existing uses of Text and emphasis in the class library. (Hint: To find references to class Text, select the class and execute command browse... and then Class refs in the class list.)
  2. Find references to text emphasizing methods.
  3. Class ColorValue lets you create objects describing colors in various mixtures of red, green, and blue, or using other specification mechanisms. Many common color combinations such as red, salmon, yellow, and blue are predefined via class messages. Find these class messages and count how many different colors are predefined. Browse references to ColorValue and note that most of them refer to 'resources' - definitions of user interface objects such as icons and windows.
  4. Write an expression that opens a window with the following text:
    ABCDEFGhijklmnop. (Hint: Use repeatedly message emphasizeFrom:to:with: from the Text protocol.)
  5. Define methods allItalic and allUnderlined to emphasize all characters in the receiver Text as indicated.

4. Class Boolean

Class Boolean is abstract and classes True and False are its concrete subclasses, each with a single instance: true and false. Booleans are used mainly for control of flow of execution including conditional execution of a block of statements and conditional repetition. Try

    (4 < 5)                     "true"
    (15 factorial < 100000000000)   "see for yourself (try this in C!)"
    (4 < 5) ifTrue: [Transcript clear; show: '4 is less than 5']                    "The ifTrue: message. To save typing, press <Ctrl> t"
    (4 < 5) ifFalse: [Transcript clear; show: '4 is less than 5']                   "The ifFalse: message. Shortcut <Ctrl> f"
    (14 < 5) ifTrue: [Transcript clear; show: '14 is less than 5']
    (4 < 5) ifTrue: [Transcript clear; show: '4 is less than 5']                    "The ifTrue:ifFalse: message - this and the following line."
	    ifFalse: [Transcript clear; show: '4 is NOT less than 5']
    (14 < 5) ifTrue: [Transcript clear; show: '14 is less than 5']
	     ifFalse: [Transcript clear; show: '14 is NOT less than 5']

The square bracket constructs containing statements constitute block closures or simply blocks. Evaluation of the statements in a block is delayed until it is explicitly requested by the definition of the message as we will see when we talk about blocks in the next section.

Notice that the ifTrue/ifFalse constructs are regular message sends. I.e. they are expressions which yield a value (they are not statements, as in many other programming languages).
Therefore, they return a result: the value of the evaluated branch:

    (3 < 4) ifTrue:[ 'less' ] ifFalse: [ ' not less' ]
or even:
    Transcript
	showCR:( (3 < 4)
		    ifTrue:[ '3 is less than 4' ]
		    ifFalse:[ '3 is not less than' ]
	       )
(this will sound familiar to Lisp or other functional language programmers).

As in all programming languages, Booleans can be combined, as in

    (3 < 4) & (5 < 6)           "logical AND"
    (3 < 4) | (5 < 6)           "logical OR"
    (3 < 4) not                 "logical negation"

The & and | binary messages are 'fully evaluating', which means that the argument is evaluated under all circumstances. This is because Smalltalk uses strict evaluation which means that a messages argument(s) are evaluated before - unless the expression is embedded in a block, which can be passed around unevaluated and forced to be evaluated later.
The and and or operators also have 'partially evaluating' versions in which the argument is only evaluated if necessary:

    (3 < 4) and: [5 < 6]        "The argument block is evaluated because the receiver is true,
				 and the expression's value thus depends on the value of the argument."
    (3 > 4) and: [5 < 6]        "The argument block is not evaluated because the receiver is false,
				 and the expression's value is thus false -
				 no matter what is the value of the argument."
    (3 < 4) or: [5 < 6]         "Argument is not evaluated."
    (3 > 4) or: [5 < 6]         "Argument must be evaluated."
    (3 factorial > 15) and: [3 squared > 50 or: [44 > 150 log]]     "Combines several logical operations."

Note that the partially evaluating version requires a block as its argument because only a block argument gives the option to be evaluated or not. If the block needs to be evaluated, it is evaluated inside the definition of the and: or or: message. The same thing happens in ifTrue: and ifFalse: messages.

Although the fully evaluating version may be easier to read, the partially evaluating versions with block arguments are generally preferred for two reasons: they may be faster because the argument does not have to be always evaluated, and they allow us to avoid evaluating an inappropriate or possibly even illegal operation as in

    | x |
    x := (Dialog request: 'Enter a number' initialAnswer: '10') asNumber.
    x > 0 and: [x log > 5]

If the user entered a negative number, attempting to evaluate the statement inside the block would cause an exception, but this does not happen with and: because the block will not be evaluated when x > 0.
On the other hand,

    | x |
    x := (Dialog request: 'Enter a number' initialAnswer: '10') asNumber.
    x > 0 & (x log > 5)

will cause an exception for x <= 0 because the argument of & must be calculated before the message is sent; in other words: always.

Exercises:

  1. Construct an expression writing text to the Transcript to demonstrate that the 'partially evaluating' version does not evaluate the block when not necessary.
  2. Write an expression to determine whether a message selector is unary and print a message to the Transcript.
  3. Read and explain the definition of ifTrue: in classes Boolean, True, and False.
  4. What does an ifTrue: message return when its receiver is true? What does it return if the receiver is false?
  5. Write equivalents of and: and or: messages using ifTrue: and ifFalse: .
  6. In Smalltalk, true and false are each single instances of corresponding classes True and False; can you explain the reason for this?

5. Blocks

Blocks are instances of class BlockClosure (in VisualWorks). They are almost always instantiated as literals using the square brackets syntax as in

    [30 squared]
    [Transcript clear. Transcript show: 'Hello']

A block represents 'delayed execution' of a sequence of zero or more statements, which means that the expressions inside the block are evaluated only if the program explicitly requests it.
C-programmers can think of blocks as a kind of "powerful anonymous function",
Lispers call them lambdas.

To validate this, evaluate the above statements with "Print it" and "Inspect it". In the inspector, send the block (self there) a value message.

To evaluate a block, in other words to evaluate the expressions surrounded by the brackets, send it a value-message, as in

    [30 squared] value
    [Transcript clear. Transcript show: 'Hello'] value
    [:x | 3 + x] value: 5           "Another block/value-message combination that will be explained shortly."

Evaluate each of these three expressions with "Print it" and note that a block returns the object calculated by its last statement.

Blocks are very important and one of their most common uses is in iteration (to be presented later) - repeated evaluation of a sequence of statements while a condition holds, does not hold, etc. To see iteration at work, study and then evaluate the following code fragment:

    | count |
    count := 0.
    [count < 100] whileTrue: [count := count + 1].          "Repeat iteration until the first block evaluates to false."
    Transcript clear; show: count printString

The evaluation of the block argument in this example is triggered by a value message inside the definition of the whileTrue: method. This definition is, of course, in class BlockClosure (Block) because the code shows that the block is the receiver. Study and evaluate also the following code fragments using different iteration messages:

    | count |
    count := 0.
    [count squared > 100] whileFalse: [count := count + 1]. "Repeat iteration until the first block evaluates to true."
    Transcript clear; show: count printString

    | count |
    count := 0.
    [count := count + 1. count < 100] whileTrue.            "Note that this is a unary message."
    Transcript clear; show: count printString

The following two forms of iteration do not use blocks as receivers - they are not defined in class BlockClosure (Block) - but I include them here because they are examples of common iterations:

    Transcript clear.
    3 timesRepeat: [Transcript cr; show: 'Testing!']

    3 timesRepeat: [(Delay forSeconds: 1) wait. Screen default ringBell]    "Make sure to turn up your speaker first!"

    Transcript clear.
    1 to: 3 do: [: n | Transcript show:n; tab; show:n squared; cr]

The last block contains a block argument "n". In this case, the argument takes consecutive values of 1, 2, and 3 during the repeated evaluation of the block. That's because the definition of to:do: dictates it, not because of some magic. Some messages are defined so that they require one or more arguments, others don't have an argument - it all depends on what is needed and how the message is defined.

Blocks may also contain internal temporary variables as in

    Transcript clear.
    1 to: 3 do: [: n| | square cube |
		      square := n squared.
		      cube := square * n.
		      Transcript show: n; tab; show: square ; tab; show: cube; cr
		]

The 'lexical scope' (visibility) of block arguments and block temporary variables is limited to the block itself and identifiers n, square, and cube are thus undefined outside the block. If you have a choice, define your temporary variables inside your blocks rather than outside - this not only makes evaluation slightly faster, but also (and this is the main point!) makes your code less vulnerable to errors.

Exercises:

  1. Write a code fragment that repeatedly asks the user for a number and prints its square root in the Transcript. When the user enters a negative number, the program stops.
  2. Message to:do: steps through its block argument values in increments of 1. Is there a method that allows you to specify the step? (Hint: The method is defined in the same class and protocol as to:do:)
  3. Use the message from the previous exercise to write a code fragment that prints a table of x, sin(x), and cos(x) values in the interval 0 to pi in increments of 0.1.
  4. Write a code fragment that displays a dialog with two choices - continue and stop. The program keeps redisplaying the dialog until the user selects stop.
  5. Find, read, and explain the definition of message timesRepeat:
  6. An interesting use of blocks is in the message millisecondsToRun: which takes a block as its argument. Read its definition, and use it to determine how long it takes to evaluate "10000 factorial" on your computer.
  7. The feature that determines whether a block argument has zero arguments (as in ifTrue:), one argument (as in to:do: above), or more than one argument is the kind of value message used to evaluate the block. Check the definition of ifTrue: and to:do: to see the difference

6. Collections

Classes defining various kinds of collections of objects are one Smalltalk's greatest strengths. They include collections with indexed elements (array, ordered collection, sorted collection, and others), and unordered collections whose elements are not accessed by index (such as sets, bags, and dictionaries). Most collections are dynamic in that their size can change at run-time, but a few (Array and its subclasses) have fixed size. Indexed collections are indexed starting from 1. Almost all collections are heterogenous (can accept any objects as their instances) but a few are homogeneous (accept only certain kinds of objects). The following is a brief overview of the essential collection classes and their main protocols:

The main protocols shared by all collections are

creation:

typically use new, new:, and various forms of with: as in

    Array new: 5
    OrderedCollection new
    Array with:1 with:2 with:3
    Array with: 3 factorial with: 5 factorial with: 23 factorial

accessing:

different kinds of collections use different accessors and only the following two methods are shared: To see the difference, evaluate the following with "Do it"

    | oc |
    oc := OrderedCollection new: 10.
    oc add: 10; add: 20.
    Transcript
	clear;
	show: 'capacity: '; show: oc capacity; cr;
	show: 'size: '; show: oc size; cr

All collections where the elements can be accessed via a key (either numeric or another object) respond to the access messages "at:" and "at:put:"
Collections which can grow and shrink and where elements can be added/removed, respond to the "add:" and "remove:" family of messages.

Arrays, OrderedCollections, Dictionaries (and others) respond to getter-messages at: and setter-messages at:put: as in

    | arr |
    arr := Array with:11 with:22 with:33.
    Transcript
	clear;
	show: 'element at index 2: ';
	show: (arr at: 2);
	cr.
    arr at: 2 put: 20.
    Transcript
	show: 'new element at index 2: ';
	show: (arr at: 2);
	cr

OrderedCollection often uses first and last, and removeFirst and removeLast for accessing. The second pair of messages returns the same object as the first pair, but removes the object at the same time. Methods in adding and removing protocols (explained below) are frequently used as setters to fill an OrderedCollection.

Of course, first, last etc. are only supported by collections which impose an order on their elements (i.e. Sets, Bags and Dictionaries do not).

Dictionaries are special and use their own accessors. Check them out in the Browser.

converting:

Converting one kind of collection into another - examine the result of

    #('abc' 'AAA' 'xyz' '123' 'def') asSortedCollection         "Use Inspect or Print it.
								 The receiver is a literal array."
    #(1 1 1 3 5 6 6 2 2) asSet
    (1 to:100) asOrderedCollection                              "Use Inspect or Print it.
								 The receiver is an Interval."
    #('abc' 'AAA' 'xyz' '123' 'def' 'abc')
	asSet asSortedCollection asArray                        "Eliminate duplication and sort an array."
    (1 to:100) as:Array

adding and removing:

These messages only apply to collections that can grow; as a consequence, they cause an exception for arrays (VisualWorks; in ST/X a warning message is printed). The main messages are add:, remove: and remove:ifAbsent: (used if you are not sure that anObject really is in the collection). A peculiarity of all these messages is that they return the argument, not the collection itself. So

    | names result |
    names := OrderedCollection with: 'John'.
    result := names add: 'Wayne'.
    Transcript show: result; cr

adds 'Wayne' to names, but the add: message returns 'Wayne', not the changed collection - so that the fragment prints only the name. To obtain the collection, cascade add: with message yourself as in

    | names result |
    names := OrderedCollection with: 'John'.
    result :=  names add: 'Wayne'; yourself.
    Transcript show: result; cr

The remove: messages also return the argument to be removed. Compare the value of

    | names |
    names := OrderedCollection with: 'John'.
    names add: 'Wayne'; add: 'James'.
    names remove: 'Wayne'

with

    | names |
    names := OrderedCollection with: 'John'.
    names add: 'Wayne'; add: 'James'.
    names remove: 'Wayne'; yourself

This peculiarity of adding and removing messages applies to other collection accessors as well and is a source of frequent mistakes, even by experienced Smalltalk programmers.
(It could be argued, if the value returned by the add:-messages should not be changed...)

Finally, read, predict, and test the following code fragment:

    | names |
    names := OrderedCollection new.
    names add: 'John';
	  add: 'Robert';
	  add: 'Wayne';
	  add: 'James'.           "We don't need yourself here because our goal is to update names, which the cascade does."
    Transcript show: 'Full collection: '; show: names; cr.
    names remove: 'Wayne'.
    Transcript show: 'After removing Wayne: '; show: names; cr

OrderedCollection can also use addLast: (which is equivalent to add:), and addFirst:

In Dictionaries the argument of add: is an Association, and remove: and remove:ifAbsent: are illegal - use removeKey: and removeKey:ifAbsent: instead. Inspect

    | dictionary |
    dictionary := Dictionary new
	add: 'overdo' -> 'do to death, go to extremes';
	add: 'overheated' -> 'agitated, excited';
	add: 'playmate' -> 'buddy, companion';
	yourself.
    dictionary removeKey: 'overdo'.
    dictionary
actually, since the associations are only present virtually (actually the dictionary stores keys and values internally in separate arrays), you can (should ?) use the alternative accessors at: and at:put: with dictionaries:
    | dictionary |
    dictionary := Dictionary new
	at: 'overdo'     put:'do to death, go to extremes';
	at: 'overheated' put: 'agitated, excited';
	at: 'playmate'   put: 'buddy, companion';
	yourself.
    dictionary removeKey: 'overdo'.
    dictionary
which is actually more efficient, as the temporary associations are not created.

enumeration:

Methods in this very useful protocol access individual elements of the collection and do something with them. This is perhaps the most interesting of all collection protocols and its methods work with all types of collections. To explore the main enumeration methods all of them regularly used by all good Smalltalk programmers, explain the following and evaluate it with "Do it":

    #(1 2 3 4) do: [:element|
	Transcript show: element squared; cr
    ]

and the following (line-by-line) with Inspect or "Print it"

    #(1 5 2 89 34 53)
	select: [:element| element > 28]
    #(1 5 2 89 34 53)
	reject: [:element| element > 28]
    #(1 5 2 89 34 53)
	collect: [:element| element > 28]
    #(1 5 2 89 34 53)
	detect: [:element| element > 28]              "Returns the first element satisfying the condition."
    #(1 5 2 89 34 53)
	detect: [:element| (element rem: 3) = 0]
	ifNone: [Dialog warn: 'No such element']
    #(1 5 2 89 34 53)
	findFirst: [:element| element > 28]           "Returns the index of the first satisfying the condition."

For dictionaries, both enumeration of associations and of association values is possible. There is even a very valuable enumeration message, which passes both key and value as separate arguments to the enumeration block:

    |d|

    d := Dictionary new.
    d at:'one' put:'eins'.
    d at:'two' put:'zwei'.
    d at:'three' put:'drei'.

    d keysAndValuesDo:[:k :v|
	Transcript show:k; show:' -----> '; show:v; cr
    ]
For functional language programmers: these are pretty much similar to map and other functions which use a function as argument (remember: blocks are the equivalent to lambda functions).

testing:

Mainly testing whether a collection contains an element - return true or false. Try

    #(1 2 3 4 5) includes: 4            "Test for presence of a specific object."
    #(1 2 3 4 5)
	contains: [:number| number squared > 50]   "Test for presence of an element satisfying a test."

Two very frequently used testing messages are isEmpty and isNotEmpty.

copying:

This protocol includes messages to copy collections completely or parts of it. Some of it is already known from the String protocol, but most of it is actually implemented for all sequenceable collections (those with a numeric index 1..).
Try and understand:

    #(10 20 30 40 50) copyFrom:2 to:4
    #(10 20 30 40 50) copyFrom:3
    #(10 20 30 40 50) copyTo:4
    #(50 20 40 30 10) asSortedCollection copyTo:4
    (1 to: 100) copyFrom:50
    #(10 20 30 40 50) ,  #(6 7 8)          ", (comma) is the concatenation message"
    'hello' ,  ' ' , 'world'
    #(1 2 3 4) , 'hello'

Exercises:

  1. Write an expression to sort an array of numbers in descending order of magnitude; repeat for an array of strings in reverse alphabetical order.
  2. Examine, explore existing uses, and test messages in the conversion protocols of collections.
  3. Examine the enumeration protocol of Collection and its subclasses and list and explain additional enumeration methods.
  4. Read and explain the definition of the yourself message. Find several of its uses in the library.
  5. Examine the protocols of Dictionary and IdentityDictionary paying special attention to adding, removing, and enumeration - these protocols are somewhat different from those of other collections.
  6. Find all senders of isEmpty and explore a few uses.
  7. Give an example illustrating the difference in the speed of IdentitySet and Set.
  8. Write an expression to return all integers between 3 and 51 that are divisible by 3 and 7. Use the most suitable enumeration message.
  9. Repeat the previous exercise but return integers that are not divisible by 3 and 7.
  10. Write an expression to return the squares of all integers between 3 and 51 that are divisible by 3 and 7.
  11. Define method allButFirst that returns the collection of all elements of the receiver except the first one. As an example, "#(1 2 3) allButFirst" should return #(2 3).
  12. Define method sameSizeAs: aCollection to return true if the receiver and the argument collections have the same size.
  13. Define method equalElementsAs: aCollection to return true if the receiver and argument collections' elements are equal. It should work for all sequenceable collections.
  14. Compare the speed and the differences in operation of = and equalElementsAs:.
  15. Define method + to add together elements of two sequenceable collections of equal size. As an example, "#(1 2 3) + #(10 20 30)" should return #(11 22 33). Ignore possible error conditions.
  16. Define method flattened which returns a sequenceable collection of all eleements of the receiver or the receivers collection elements' elements in the original order. As an example, "#(1 (2 3 4) (5 6 7) (8 (9 10))) flattened" should return #(1 2 3 4 5 6 7 8 9 10). Ignore possible error conditions.

Part 6. Other important classes

This part of the introduction covers the fundamentals of the following additional classes

Study the online documentation and browse the class library and the list of parcels to learn more.

1. External streams, files, and binary object streaming service (BOSS)

The following presentation focuses on the use of files to store your application's objects in files and read them back. It presents basic storage-related classes in the following order:

Filename

Class Filename provides unified platform-independent access to files and directories. Because file systems are platform-dependent, Filename is an abstract class and the real work is implemented by its concrete subclasses. This dependency is hidden because when you create a Filename the method creates an instance of the default concrete class for the current platform. The following are the essential Filename protocols and methods and a few examples of their use:

instance creation

A Filename (which may represent a file or a directory) is created with message named:aString where aString is the name of the file as in

    Filename named: 'notes.txt'     "Refers to an existing or non-existant file in the current directory but does not create it."

You can also create a Filename object from a String with asFilename as in

    'C:\temp\help\notes.txt' asFilename   "Example with complete file access path."

Class-side utilities protocol provides a number of useful methods such as

    Filename filesMatching: '*.st'          "Return the names of all files in the current directory matching the argument."

    Filename volumes        "Return the names of all reachable disk volumes (disk drives)."

Class-side constants protocol provides access to platform-dependent conventions as in

    Filename separator      "Return this platform's separator in file path. Frees code from the platform-dependent syntax."

defaults provides access to the current directory, as in

    Filename currentDirectory       "Return the Filename representing the current directory."

and other information.
The following are Filename instance-side protocols:

utilities

provides access to file creation time and other dates, its contents (without having to open or close it), and so on, as in

converting

testing

parsing

parses the filename path of the receiver; note that the file described by the path does not have to exist. However, as fileName parsing depends on the operating system, the name syntax depends on the OS you are running on.
ms-dos:

    'C:\tmp\mydirectory\test.txt'  asFilename head       "return the directory prefix of the receiver - here  'C:\tmp\mydirectory'"
    'C:\tmp\mydirectory\test.txt'  asFilename tail       "return String with name of receiver without path, here  'test.txt' "
    'C:\tmp\mydirectory\test.txt'  asFilename extension  "return String with receiver's extension, such as '.txt' "
    'C:\tmp\mydirectory\test.txt'  asFilename directory  "return the Filename representing the receiver's directory
							  or the receiver itself if it denotes a directory,
							  similar to message head except that it returns a Filename object
							  rather than a String."
unix:
    '/tmp/mydirectory/test.txt'  asFilename head
    '/tmp/mydirectory/test.txt'  asFilename tail
    '/tmp/mydirectory/test.txt'  asFilename extension
    '/tmp/mydirectory/test.txt'  asFilename directory

Exercises

  1. Print the names of all directories in the root directory of the current disk drive.
  2. Print the names of all files in the root directory of the current disk drive.
  3. Print the names of all read-only files in the root directory of the current disk drive.
  4. Open the File editor tool from the Launcher and use it to create a short text file in the root directory.
  5. Print its contents of the text file from the previous exercise in the Transcript.

Althoug Filename provides an interface to the file system, it does not provide many messages to operate on the contents of the file except contentsOfEntireFile and a few others. This is the role of various external stream classes that are covered next.

ExternalStream

External streams are subclasses of Stream and form one of the branches of its subtree. The other very important branch contains internal streams that are used mainly for efficient manipulation of strings and other sequenceable collections. Many of the messages described below are inherited from abstract superclasses common to both internal and external streams but their description is written in the context of external streams.

The branch leading to external streams forms a rather large hierarchy of mostly abstract classes and the following instance variables are essential for basic stream use:

The following summarizes the essential behaviors required for use of streams with files:

stream creation

External streams are created over Filename objects by messages in the stream creation instance protocol in class Filename. Several types of streams can be created and they differ in how they access the underlying file. The basic types are read stream, write stream, and read-write stream but other types of access can also be prescribed. The following are examples of the essential messages:

External streams can be operated in two modes: the mode is changed by sending corresponding messages to the stream.

testing

byte-oriented reading and writing

In accessing a text file, nextPut: would be used with a Character argument and nextPutAll: would be used with a String argument.
In binary mode, the arguments must be small integer values or collections of small integers respectively.

status

Examples

Store text in a file called "test.txt"

    | filename writeFileStream |
    "Create a file and a write stream over it,
     store string in it, close."
    filename := 'test.txt' asFilename.
    writeFileStream := filename writeStream.
    writeFileStream nextPutAll: 'testing, testing'.
    writeFileStream close

Read the data back one character at-a-time displaying the characters in the Transcript one per line, close file and delete it:

    | filename readFileStream |
    "Read from file created above,
     send contents character-by-character to Transcript,
     close file, delete it."
    filename := 'test.txt' asFilename.
    readFileStream := filename readStream.
    [readFileStream atEnd] whileFalse: [
	Transcript nextPut: readFileStream next; cr
    ].
    readFileStream close.
    filename delete

Notes:

BinaryObjectStorage

BOSS (for BinaryObjectStorage) is the name of a collection of VisualWorks and Smalltalk/X classes used to store objects in files and read them back. BOSS is not a part of the basic image and if you want to use it, you must load the BOSS parcel (in VisualWorks). BOSS is very powerful and can store very complicated objects such as a sorted collection of Book objects. It does, however, have some limitations and the major one is that it cannot store objects that 'belong' to the operating system such as windows of graphical user interfaces and objects that reference them. This can be quite annoying because windows may, for example, be indirectly referenced by names of Book authors displayed in a list in a window.
To avoid this, the code that uses BOSS to save such objects usually uses copies or (better yet), strictly separate the data from the representation in that the book objects do not at all refer to any GUI related objects (MVC-Pattern).

For routine use, the only BOSS class you need to know is BinaryObjectStorage. It includes the streaming protocol and to read or write objects using BOSS you just use the accessing and positioning methods described above. The only other methods you need are onNew:aStream (to prepare a Stream for writing) and onOld:aStream (to prepare to read objects from an existing file).

Example

Store an Array in a file and read it back.
The code is rather redundant - I could achieve the same result on fewer lines and with fewer variables - but this version clearly indicates the objects involved. A shorter version is given in the next example.

    | array filename writeFileStream boss |
    "Create file and a write stream over it, store an Array in it using BOSS, close."
    filename := 'test.bos' asFilename.
    array := #('string 1' 'string 2' 100 200).
    writeFileStream := filename writeStream.
    boss := BinaryObjectStorage onNew: writeFileStream.
    boss nextPut: array.
    boss close

    | array filename readFileStream boss |
    "Open a read stream over the file created above, read the Array from it using BOSS, print it, close and delete file."
    filename := 'test.bos' asFilename.
    readFileStream := filename readStream.
    boss := BinaryObjectStorage onOld: readFileStream.
    array := boss next.
    Transcript clear; show: array.
    boss close.
    filename delete

Note that the whole array was stored as one object rather than a sequence of elements. The following variation stores the elements separately and reads them back one-by-one:

    | array boss |
    "Create file and a write stream over it, store individual elements of an Array object in it using BOSS, close."
    array := #('string 1' 'string 2' 100 200).
    boss := BinaryObjectStorage onNew: 'test.bos' asFilename writeStream.
    boss nextPutAll: array. "nextPutAll: stores the collection's elements one-by-one."
    boss close

    | filename boss |
    "Open a read stream over the file created above, read the objects one-by-one using BOSS, print them, close, delete file."
    filename := 'test.bos' asFilename.
    boss := BinaryObjectStorage onOld: filename readStream.
    Transcript clear.
    [boss atEnd] whileFalse: [Transcript show: boss next printString; cr].  "Typical pattern for reading files."
    boss close.
    filename delete

With random access, BOSS could be used as a simple database program.

Exercises:

  1. Create a two-element array whose first element is the array of all even numbers between 1 and 10 and whose second element is the array of all odd numbers between 1 and 10. Store this object in a file as a single object using BOSS. In another code fragment, read this object back and print it to Transcript.
  2. Use the same array as in the above exercise but store it as a sequence of elements (a 10-element sequence of objects consisting of 2, 4, 6, 8, 10, 1, 3, 5, 7, 9). In a second fragment, read the objects back into a single OrderedCollection and inspect the result.
  3. Simpler objects can also be stored and re-stored without using BOSS using messages storeOn: aStream and readFrom: aStream. Message storeOn: creates a Smalltalk expression that describes the object and stores it in a stream as a human-readable String. Message readFrom: uses the compiler to recreate the object from the expression. This method of storing and restoring is unsuitable for more complex objects. The following example illustrates the use of this approach with internal streams - external streams would be used similarly. Note that you don't need to close an internal stream.

    | oc readStream writeStream |
    " 'Store' an object in an internal stream."
    writeStream := WriteStream on: String new.
    #(10 20 30) asOrderedCollection storeOn: writeStream.
    Transcript show: writeStream contents; cr; cr.
    readStream := ReadStream on: writeStream contents.
    oc := Object readFrom: readStream.
    Transcript show: oc

2. Exceptions

Good programs must be prepared for illegal conditions such as attempts to divide by a variable whose value is 0, reading from a file that does not exist, or accessing an array index outside its legal bounds. One way to deal with such conditions is to detect them and prevent problems, another is dealing with the problem when it occurs. Prevention consists of conditional logic with expressions such as collection detect:ifNone: or aFilename exists ifTrue: [...] ifFalse: [...], whereas the problem handling approach uses Exception objects. This section explains the principles of Exceptions.

A Smalltalk Exception is an object designed to help in handling error conditions, an instance of one of the subclasses of Exception. According to VisualWorks documentation, 'exceptions are unusual or undesired events that can occur during the execution of a VisualWorks application', and exception handling consists of defining how to handle a particular exception and 'raising an exception' (instantiating one of the exception classes) when the 'unusual or undesired event' occurs.

Claus: I dont like this definition, it gives too much of an impression of an exception always being an erronous situation. However, once we dont do think so, there appear many other useful applications of the exception mechanism, such as non-local gotos, very late binding, notifications and queries etc.
So, an exception could also be defined as an irregular control flow.
Its as usual: for a hammer, everything looks like a nail!

To use the exception-handling system, one must understand
- the classes that define exception objects
- the methods that allow exception handling, and
- the principle of their operation.

The following is a brief summary of these topics; VisualWorks users should read the Application Developers Guide for more details, Smalltalk/X users may want to read the ST/X online documentation on Exception handling.

Exception classes and how to raise an exception

All exceptions are subclasses of class GenericException. This branch of the class tree is quite large as you can see by printing

    GenericException allSubclasses size

Class GenericException defines a number of instance and class variables. They include messageText (the error string), originator (the object that instantiated the exception), notifierString (the default string used to describe the receiver), initialContext (the initial context of the message send in which the exception was raised), and others.

The most important subclasses of GenericException are Error, Warning, and Notification and the difference between them is in the severity of the event that raised them. Error is the superclass of exception classes raised when a serious program error that requires an intervention occurs; its subclasses include ArithmeticError, DomainError, ZeroDivide, RangeError, MessageNotUnderstood, KeyNotFoundError, SubscriptOutOfBoundsError, and many others. The default response to an Error is to open a notification window or debugger as happens, for example, when you evaluate

    #(1 2 3) at: 4

but the program may define a handler that handles the situation programmatically and eliminates the exception window. I will show this shortly.

Warning is a less serious exception raised when the user should be to be notified of some exceptional event by a dialog rather an exception window.

The default action for a Notification is to do nothing and continue execution; the presence of this exception can, however, be handled using the same techniques as with other exceptions.

You can define your own exception classes as subclasses of Exception and activate (raise) them at appropriate places of your program with messages raiseSignal or raiseSignal: aString. More commonly, you will only need to intercept (trap) some of the predefined exceptions and handle them in ways similar to the examples included in the class-side protocol examples of class Exception. Note, however, that (some of) these examples are written in terms of the older exception handling mechanism that used Signals rather than exceptions. In essence, Signals map to exceptions and have been introduced for conformity with the ANSI Smalltalk standard.

Handling exceptions

Every exception has its default handler action (method defaultAction, usually inherited) which is executed when the exception occurs and the program does not provide an explicit error handling action.
As an example,

    3 / 0   "Handled with default action. For explicit handling by program, see example below."

by default opens the familiar Exception window, whereas a Warning by default opens a dialog.

If you anticipate the possibility of an exception and want to handle it, you must 'guard' the operation that might create the exception by inserting it in a block, and send it the message on: anExceptionClass do: handlerBlock. The on: argument is the name the class of the anticipated exception, and handlerBlock is a block that will be evaluated if the exception of the specified class or its subclass is raised during the evaluation of the guarded block. As an example, to guard against division by 0, you could write

    | divisor |
    divisor := (Dialog request: 'Enter divisor' initialAnswer: '0') asNumber.
    [10 / divisor]          "Operation to be attempted."
	on: ZeroDivide
	do: [:exception |
		Dialog warn: 'Attempt to divide by zero.'
	    ].
    Transcript show: 'divisor = ', divisor printString

When this code fragment is evaluated and divisor ~= 0, the receiver block performs the indicated division and evaluation proceeds to the Transcript statement. When divisor = 0, the handler evaluates the do: block and proceeds to the Transcript statement.

The principle of the the operation of Smalltalk error handlers is as follows:
As the receiver block of on:do: is being evaluated it produces a sequence of nested messages sends in the usual way.
Each invoked message puts an 'evaluation context' object with information about the message, its receiver, and its arguments on an evaluation stack.
Quite possibly, a large number of contexts are thus piled up on top of the context of the method evaluating the on:do: message as evaluation of the block proceeds.
If the evaluation of a message succeeds without raising an exception, the piled up messages unwind one-by-one and eventually strip the stack down to the context object of the on:do: message.
If, however, a message executed before the guarded block is fully evaluated raises an exception, Smalltalk starts searching the context stack from this message's context (now on the top of the stack) until it finds an on:do: message whose on: argument is the exception class that was raised, or its subclass.
It then evaluates the do: block and execution proceeds from this point.
If a matching on:do: message is not found, the default action is invoked opening an Exception window.

Excercises

  1. Modify the example above to open an inspector when the exception occurs. Inspect the components of the exception.
  2. Browse the Exception hierarchy and examine the instance variables and methods defined in Exception and its subclasses.
  3. Write code fragments that will generate the following exceptions and handlers to deal with them.
    IndexNotFoundError - raised on attempt to access a non-existent collection index
    KeyNotFoundError - raised on attempt to access a non-existent dictionary key
    NotFoundError - raised on attempt to remove a non-existent collection element
    PositionOutOfBoundsError - raised on attempt to position a stream outside its limits
  4. What is the difference between instance variable messageText and class instance variable notifierString, both defined in class GenericException?

Other ways of responding to exceptions

The do: exception provided to the handler block can be asked to respond in special ways. Two of them are illustrated below and others are described in VisualWorks or ST/X documentation and demonstrated in several examples in class Exception. All these methods are defined in class GenericException and you can, of course, define your own additional methods using information available in Exception objects if you wish.

retry
instead of exiting from the protected block after handling the exception, re-evaluate it, possibly modifying some objects first to make the re-evaluation possible

    | divisor |     "On exception, give divisor a new but reasonable that will succeed."
    divisor := (Dialog request: 'Enter divisor' initialAnswer: '0') asNumber.
    [10 / divisor]          "Operation to be attempted."
	on: ZeroDivide
	do: [:exception |
		Dialog warn: 'Attempt to divide by zero.\Replacing divisor.' withCRs.
		divisor := 0.000001.
		exception retry
	    ].
    Transcript clear; show: 'divisor = ', divisor printString

resume
instead of exiting, continue processing the protected block immediately behind the point where the exception occurred; this example traps the interrupt key <Ctrl> y (ST/X: <Ctrl> c) pressed by the user during execution:

"Press <Ctrl> y (ST/X: <Ctrl> c) repeatedly to see the effect of the following."

    [1 to: 1000 do: [:i | Transcript show: ' ', i printString; cr. Delay waitForSeconds:0.01. ]]
	on: UserInterrupt       "Trap <Ctrl> y (ST/X: <Ctrl> c),
				 which normally causes user interrupt and opens an exception window."
	do: [:ex |
	    Dialog warn: 'You pressed the Interrupt key'.
	    ex resume           "Return to the loop inside the guarded block."
	]

Another way to deal with exceptions is to use ensure: aBlock or ifCurtailed: aBlock messages. As on:do:, both are defined in class BlockClosure and start by evaluating the receiver. The ensure: message then evaluates the block argument aBlock - whether the receiver block is evaluated successfully or not. A typical use of ensure: is in processing files where we must ensure that the external stream is closed whether the file operations succeeded or not.
The pattern is as follows:

    [open stream on Filename and do something]
	ensure: [stream close]  "Close stream whether the operations in the block succeeded or not."

or

    | divisor |
    divisor := (Dialog request: 'Enter divisor' initialAnswer: '0') asNumber.
    [10 / divisor]
	ensure: [Dialog warn: 'Division may or may not have succeeded.']

Message ifCurtailed: aBlock evaluates aBlock only if the evaluation of the receiver fails.
As an example,

    | divisor |
    divisor := (Dialog request: 'Enter divisor' initialAnswer: '0') asNumber.
    [10 / divisor]
	ifCurtailed: [Dialog warn: 'Division by zero attempted.']      "Click Terminate in Exception window on exception."

If the divisor is not 0, the argument block is not evaluated.

As you have noted, these two messages do not handle the raised exceptions, which perform their default actions (such as opening an exception window) but 'tolerate' (ensure:) or 'sense' (ifCurtailed:) their occurrence.

Exercises

Part 7: Creating Graphical User Interfaces

Note: This chapter certainly needs more flesh.

This section explains the basics of creating a graphical user interface (GUI) for your application. Read on-line help or VisualWorks or ST/X documentation for more details.

Typical application development starts with 'domain' classes followed by the development of the GUI. As an example, in a library information system, you would first create classes representing books, patrons, catalogs, and other library objects, and when you are sure that they work you would then create the windows for interacting with them. To create the windows, you would use the UI Painter tool to 'paint' widgets on a blank window ('canvas') and write the 'glue' code to interface the GUI to your domain objects. You could also create windows programmatically, possibly even at runtime.

The UIPainter is accessible via a button on the Launcher or through the Tools menu. Its interface consists of three windows: the canvas itself, a Palette with widgets, and a Painter tool. To create a window, you can proceed as follows:

  1. Use the Painter tool to define the properties of the canvas (the future window) such as its label, the name of the method that will supply its menu and tool bars (menus themselves can be created using the Menu Editor tool), its size constraints (minimum, fixed, etc.), its background color, and its scroll bars. When you are done, use the Install (Save) command (this and other commands are available through the Painter tool and through the <operate> menu of the canvas) to 'install' the canvas on a class, such as NewApplication, usually a subclass of ApplicationModel. For the less common 'modal dialogs', the window will be a subclass of SimpleDialog. Specify the name of the class method that will hold the window's description (normally windowSpec). If the class does not yet exist, UI Painter will define it. Once the canvas is installed, you can open it via the Open command or programmatically via the expression NewApplication open, and browse the application model class to edit its definition.

    Besides storing a description of your window, the application model class (our NewApplication) has several other functions. As I already mentioned, NewApplication open will open the GUI and probably the whole application with it. In doing this, it will create an instance of UIBuilder which will build the window from the specification stored in windowSpec, sending several intermediate 'hook' message to the application model, the new instance of your NewApplication. By default, these messages do nothing but you can redefine them to intervene into the building process, for example to initialize the contents of some window widgets. The application model also provides an interface between the GUI and your domain objects so that, for example, when the user enters the name of a book, the application model propagates the value to your library classes. Finally, the builder provides a permanent interface to the window and allows you, for example, to enable or disable widgets, change their labels, etc.

  2. Drag the desired widgets (buttons, input fields, lists, etc.) from the Palette to the window. Their exact positions and size are not important because they can be easily accurately adjusted and aligned.

  3. Define widget properties by selecting the widgets one-by-one, entering the properties into the Painter tool, and clicking "Accept". Different kinds of widgets have different properties and you will have to learn about them from On-line Help or from the manual. The essential properties are as follows: -- name (a String) displayed inside or next to certain widgets -- action (a Symbol) - only for buttons - the name of the message sent when the user clicks the button -- aspect (a ValueHolder), an object holding the widget's value, for example the text displayed by a text widget or the labels displayed inside a list -- ID (a Symbol), used to access the widget, for example to hide or disable it.

  4. Adjust the size and position of your widgets in the canvas either by dragging them with the mouse, or by using alignment tools in the Palette. Each corner of each widget can use absolute positioning (offset) in pixels, or relative positioning (proportion of window size), or the combination of the two. Relative positioning causes widgets to automatically adjust when the user changes the size of the window. The choice can be made via Layout in the Painter tool.

  5. Use Define under Edit to have the Painter create instance variables to hold your widget aspect properties, and method 'stumps' (do-nothing methods) for any widgets that require methods, such as action methods for buttons and aspect accessors for aspects. You will fill in the bodies later.

  6. Install the window again to capture the changes in the windowSpec method. In general, if you modify the canvas and don't re-install it, the changes will not be recorded in the class and will not show when you open the window. This is a frequent cause of surprises.

  7. Use a Browser to write the code required by the GUI. includes requires at least some of the following: -- Add instance variables for access to your domain objects. As an example, if the GUI provides access to a Book, you probably need an instance variable to refer to it. -- Redefine any 'hook' messages sent to your application during the building of the window to intitialize your application and its GUI. These messages include initialize which is sent as a result of instantiating your application model class (LibrarySystemUI), and several messages sent subsequently by the UIBuilder: Message preBuildWith: is sent before the builder constructs the window, postBuildWith: when the builder is done building but before the window opens on the screen, and postOpenWith: when the window displays but before the use starts interacting with it. Each of these hook messages has its proper uses but I leave the details for the more detailed documents. The following are the most common actions performed by hook messages: -- Initialize instance variables providing access to your domain objects: Since the application model probably opens the whole application, you will want to assign new instances of your domain objects to instance variables or read them from files. This is usually done in method initialize. --- Initialize widgets' aspect variables so that the window opens with desired initial settings of lists, radio buttons, check boxes, and so on. Use postBuildWith: and expressions like anAspect value: newValue to assign a new value to an aspect variable, and anAspect value to retrieve the value; here anAspect is the widget's aspect variable. Using value and value: accessors (here and everywhere else in your code) is essential because if you try to change the value of an aspect by assigning to its instance variable, you will ruin the bindings between the application model and the widgets and the widgets and their values will become disjoint. This is a very frequent mistake. -- Specify the names of the messages that should be automatically sent to the application model at runtime when the aspect of a widget changes - as when the user selects a different radio button causing perhaps the list labels to change, or when the state of a check box changes, or a new item is selected in a list. This is achieved by a message like anAspect onChangeSend: aSymbol to: self - in postBuildWith: Here aSymbol is the name of the message sent to the application model when the change occurs.

  8. Write methods implementing button actions; if you used Define, the stump methods already exist.

  9. Write methods corresponding to changes - methods named aSymbol in your onChangeSend:to: expressions in step 7.

  10. If you need to interact with a widget (for example, to disable or enable it) access to it is provided by (builder componentAt: idSymbol) widget where idSymbol is the ID property of the widget.

That's it!
Note that you can test all of your code and your installed window at any point by opening your application. In fact, you can even change your methods while the application is open without closing the application and develop the interface interactively. This is because application model messages are sent only when they are activated by the user and can thus be changed at any point. The only times when you must close and reopen the window is when you must reinstall it (to add or remove widgets, reposition them or change other properties, etc.), or when you want to change the initialization process and therefore need to work with a window different from the one currently displayed.

There are, of course, lots of details that you must learn to build a more sophisticated GUI and you can find them in On-line help and in the documentation. The most important additional details concern the nature of the widgets. The essential facts about the basic widgets include the following:

Exercises

  1. Create an application implementing a simple counter. The window should have two buttons labeled Up and Down, and an input field showing the current count. When the window opens, the field is initialized to 0, and clicking the buttons increments or decrements the displayed count. This problem is so simple that you can do without a domain class and implement the whole application in your application model class.
    (Note: When defining the input field, select its Type as Number so that you can assign a numerical value to it directly through the "value:"-message without first converting it to a String.)
  2. Add Save and Load buttons to the counter. When the user clicks Save, a dialog requests a file name and stores the current count in it. Clicking Load requests a filename and displays the stored value in the input field.
    Hint: see the class protocol of Dialog for many common dialogs...
  3. Create an application displaying the names of available colors. It will have a list displaying the names of all color constants (class ColorValue (STX: Color) ) and when the user clicks one, the background of the window changes to the selected color. (Hint: Your application model can obtain the window via its builder.)
  4. Create an application displaying a list of all classes and showing the name of the superclass of the selected class in an input field.
  5. Create an application that allows you to view, edit, save, and load information about your books (CD collection, etc.).
  6. Read the comments of classes ApplicationModel, SimpleDialog, and UIBuilder.
    VisualWorks users: Read class-side examples in UIBuilder to see how to build windows programmatically.

Part 8: Developing a Smalltalk application

Note: the following applies to VisualWorks users; corresponding ST/X text has to be added.

1. What is a Smalltalk application?

A software application in conventional programming language usually consists of an executable (.exe) file and supporting files. The structure of a typical VisualWorks application usually consists of two main files:

The image can be ported among several different platforms without any change provided that it does not use platform-specific features such as platform-specific syntax of filenames. Only the virtual machine executable is platform-specific. For Windows applications, you can combine the two files into a single .exe file as explained in the documentation.

The development environment that you are now using is also essentially an application, but it uses two additional files. One contains the source code and has extension .sou (this file is not usually supplied with a commercial application), and the other is the 'changes' files with extension .cha, which contains all the changes that you made to the image and the source code. The .cha file allows you to restore modified code up to the point where the changes file has been created or 'compressed' into the image; this topic is covered in the documentation. A commercial application would not have a changes file because it would be compressed into the image file. In addition, the image file would be 'stripped' of all classes that the application does not need, such as Browser classes, UI Painter classes, and so on.

Most of the classes in an application usually come from the built-in library (numbers, strings, collections, windows, etc.), some possibly modified by the programmer. Application-specific classes - for example Book, Patron, Librarian, and Catalog for a book library application - are either developed specifically for the application or come from another source. Some classes may be obtained from parcels separately loaded into the basic image.

As already mentioned, the classes that constitute the application can usually be conceptually divided into three basic groups:

Creating a new application thus requires defining new methods and classes in the domain category, and application model classes defining windows and their interfaces to domain classes. Changes to built-in classes are much less common but not unusual.

2. Developing an application

The subject of methodologies used to discover classes and methods needed to implement a specification is beyond the scope of this introduction but many Smalltalk programmers like to use Extreme Programming (XP) or its variation. The process is described in several books referenced at the end and it relies heavily on frequent testing based on a testing framework that automates the testing process. The SUnit group of classes implements the framework and provides a simpleGUI to run the tests. Another essential part of XP is frequent refactoring of code (moving methods between classes, creating abstract classes when appropriate, renaming, etc.). The Refactoring Browser is a powerful tool that makes refactoring much easier and safer. You can open it from the Launcher.

3. Deploying an application

Delivering (deploying) an application means delivering an image file containing the classes needed to run the application and the virtual machine executable supplied with VisualWorks. (Re-read your license to see what you can legally deliver.) Although your application has been created using the development image, this image contains a lot of code that the application does not need, such as the Browser, the Inspector, and many other classes necessary only for development. To decrease the size of the image file, 'strip-off' as many unnecessary classes as possible using a 'stripper' ('packager') program, such as the RuntimePackager. The procedure is explained in VisualWorks documentation.

Exercises

  1. Deploy one of the applications developed earlier by using the deployment procedure described in documentation.

4. Parcels

When you start a virgin VisualWorks Smalltalk, the code library contains only the most important code. Many other interesting and useful classes are stored in pairs of .pcl and .pst files containing parcels of code. They include On-line Help (parcel VWHelp - loaded automatically when you request help), advanced tools for code development, BOSS, special kinds of numbers, a code management tool for programming teams (the StORE parcel), and many others. You can also create your own parcels to hold your own code separate from the rest, save them in a file and thus reduce the size of your image, and share them with other Smalltalk users. You can also send your parcels to Cincom and they may be added to a future release. To load, save, browse, or create parcels, open the Parcel Browser from the Launcher using either the Browse -> All Parcels command or the button with a picture of a parcel on it.

Like all other components of VisualWorks, parcels are programmable objects and advanced programmers commonly access them from their programs. See the documentation for more details.

5. Smalltalk style

Just as programmers using other languages, Smalltalk programmers have their opinions on what is good programming style. Perhaps the essential guiding principle is that they consider readability to be of paramount importance - more than programmers in most other languages. They think that code should be succinct, self-documenting, and no other documentation should normally be required to help the experienced reader understand the code. This leads to numerous guidelines summarized in the widely recognized books by Skublics and Beck. Within this limited space, I will list only a few selected Smalltalk style rules:

Part 9: What next

Now that you learned the basics, explore Smalltalk in more depth:

When studying the source code in the Browser, consider the following:

References

Smalltalk books


There are a number of Smalltalk books and the following are a few of them: There are also several recent books about Squeak, a very popular dialect of Smalltalk, Dolphin Smalltalk, and IBM's VisualAge Smalltalk.

Smalltalk Web sites


There are many Smalltalk Web sites and the following is only a small selection:

Smalltalk newsgroups

Extreme Programming (XP)


The following are a few of a number of booksabout XP, a development process favored by many Smalltalk programmers.

Refactoring

Finish

Have fun !