In the preceding chapters, you have learned Smalltalk versions of techniques which are common to most programming languages. For example, you have learned how to form basic expressions, and how to use loops and conditional statements. In this chapter, you will learn some of the concepts that make Smalltalk unique: class and method. You will examine some Smalltalk/V classes and methods and add new methods to these classes for numeric processing, pattern matching and graphics. You'll also learn how to use Inspectors, windows for viewing and changing the internal variables that define the state of objects.
Beginning with this tutorial, you will make changes to the Smalltalk/V environment itself. In order to make these changes permanent (so that you can use them in later tutorials), be sure to save the image whenever you leave Smalltalk/V. That is, when you select Exit Smalltalk/V ... from the System menu of the Transcript window, be sure to click the Yes button in the Exit dialog.
As always, you can find the examples for this tutorial in the disk file chapter.5. Use the Open item in the File menu to retrieve them, if you wish.
Problem solving using Smalltalk involves classifying objects according to their similarities and differences. You've already seen the external behavior of objects by sending messages to them and observing the results. A class defines the behavior of similar objects by specifying their insides--the variables they contain and the methods available for responding to messages sent to them.
Every object is an instance (member) of a class. For example, #(l 2 3) and #(sam joe) are instances of class Array, whereas 'north' and 'south' are instances of class String. All objects know to which class they belong. For example, evaluate the following expressions:
#(Francesca Jackie Marisa Bree) class 'Rakesh Vijay Charles Robin Tyler' class Turtle class
An object's internal variables are called instance variables; they are themselves containers for other objects. For example, objects in class Fraction have instance variables numerator and denominator. For the object representing the fraction 1/7, the instance variable numerator contains the object 1, and the instance variable denominator contains the object 7.
Methods are Smalltalk code, the algorithms that determine an object's behavior and performance. They are like function definitions in other languages. When a message is sent to an object, a method is evaluated, and an object returned as a result. Evaluate the following message expression:
(1/7) numerator
When the message numerator is sent to the fraction 1/7, Smalltalk evaluates the method numerator defined in class Fraction:
numerator ^numerator
The first line of the method defines the method name. (Notice that it matches the selector in the corresponding message.) The second line returns the result numerator, the instance variable of the receiver fraction object. As a more complex example, evaluate the following message expression:
(2/3) * (5/7)
Sending the message * to the fraction 2/3 with the fraction 5/7 as the argument evaluates the method * in class Fraction:
* aNumber ^(numerator * aNumber numerator) / (denominator * aNumber denominator)
The first line defines the method name (*) and the name for the argument, aNumber, which is used in the rest of the method to represent the argument object. The method returns a new fraction whose numerator is the product of the receiver and argument numerators, and whose denominator is the product of the receiver and argument denominators.
Notice that numerator and denominator appear both as instance variables and messages in this method. In processing the example message, the argument aNumber contains the fraction 5/7, while the instance variables numerator and denominator contain 2 and 3 respectively.
As you can see from this example, Smalltalk objects are abstract data types. The multiply method operates on behalf of the receiver object (2/3), whose internal variables numerator and denominator are accessible. The argument is another object (5/7). Even though it is the same class as the receiver, its internal variables are not available in this method, and so messages must be used to request the desired information. This Smalltalk feature provides complete safety from outside manipulation.
In using the Smalltalk/V environment up to this point, you may be wondering where you do your actual programming. To program in Smalltalk/V, you use a special window called the Class Hierarchy Browser. It lets you browse and change existing class and method definitions, and create new ones. Open a Class Hierarchy Browser by evaluating the following expression:
ClassHierarchyBrowser new openOn: (Array with: Integer with: Fraction with: String with: GraphicsDemo)
A new window will appear on the screen. You can resize or move this window as with any Smalltalk/V window. A Class Hierarchy Browser window is now available for the classes Integer, Fraction, String and GraphicsDemo, as you can see from the top left pane. Select the entry for class Fraction; you'll see the following window:
Figure 5.1
Class Hierarchy Browser
The top right pane shows the methods defined for class Fraction, the selected class. The middle pane, between the classes and methods panes, lists the variables defined in and inherited by the selected class. The bottom pane shows the class definition message for class Fraction. The class definition message shows the characteristics that make up a class. Notice the instanceVariableNames: argument; it's a string specifying that the instance variable names are numerator and denominator which are also displayed as items in the variables list pane. You'll learn about the class definition message's other arguments later in the chapter.
Select the method * in the top right pane; the source code for the method appears in the bottom pane. This pane is a text editor which you can use to change existing methods and create new ones. Try selecting other methods, and look at their source code.
Now let's add the following new method to class Fraction:
fraction "Answer the receiver minus its integral part." ^self - self truncated
This method returns a fraction less than one: the receiver of the message minus the integral part of the receiver. The method contains the word self, a special variable representing the object which is the receiver of the fraction message. Add the method to class Fraction using the following steps:
Smalltalk/V compiles the new method and installs it in class Fraction. Try it out by evaluating the following messages:
(22/7) fraction (2/3) fraction
You have seen several messages which create new objects, such as:
'bigger', ' string' 1 / 3
Classes are also objects, and so can be used in message expressions. A common way to create a new object is to send a message to its class. For example, evaluate each of the following messages with Show It:
Array new: 10 Array new Pen new Date today Time now
The first message creates an array with 10 elements, all initialized to the object nil. The object nil is the sole instance of class UndefinedObject; it is assigned to the instance variables of all new objects. This means that unless an object assigns a value to its instance variables, they contain nil. The second message, however, creates an array with no elements at all.
The third message creates a pen, an instance of class Pen; when you Show It, the Pen object displays itself as "a Pen". This is the default way for an object to display itself. (In Chapter 7, you'll learn how to include more information when an instance displays itself.)
The final two messages create an instance of class Date (representing the current date), and an instance of class Time (representing the current time), respectively.
Objects can contain both named and indexed instance variables. Named instance variables are accessed by name, as with numerator for fraction objects. Indexed instance variables are identified by integers beginning with 1. They are always accessed by messages such as:
'location' at: 2 'parts' at: 5 put: $y
An object's class specifies the named instance variables and whether or not indexed instance variables can be used in its instances. The number of named instance variables is fixed for all instances of the class. The number of indexed instance variables is defined when you create the object and may differ among instances of a class. For example, the two strings above have eight and five indexed instance variables, respectively.
For a complete description of how to specify class information, refer to Part 3 of this manual.
A powerful programming technique is recursion. Recursion is often used when an algorithm or data structure is defined in terms of itself. In Chapter 3, you saw examples using the factorial message. Let's look at the factorial method, defined in class Integer:
factorial "Answer the factorial of the receiver." self > 1 ifTrue: [^(self - 1) factorial * self ]. self < 0 ifTrue: [ ^self error: 'negative factorial' ]. ^1
The factorial method multiplies the receiver by the factorial of the receiver minus one. If the receiver is less than or equal to one, the answer is one. As in this example, a recursive solution is often a straightforward translation of a mathematical definition into a Smalltalk method.
Evaluate the following expression, which sends the factorial message to each of the elements of an array and returns the answers in a new array:
#(0 1 2 3 4 10 15 20) collect: [ :n | n factorial ]
As another example of recursion using integers, consider how to compute Fibonacci numbers. A Fibonacci number is a statistical function used in many applications. The nth Fibonacci number for n greater than 2 is defined to be the sum of the Fibonacci numbers for n - 1 and n - 2. (The Fibonacci number for n less than 3 is one.) Use the Class Hierarchy Browser to add the following method to class Integer (note that you can copy the method from the tutorial file chapter.5 and paste it over a new method template in the bottom pane of the Class Hierarchy Browser):
fibonacci "Answer the nth fibonacci number, where n is the receiver." ^self < 3 ifTrue: [1] ifFalse: [ (self - 1) fibonacci + (self - 2) fibonacci ]
Notice that the fibonacci method returns its result differently from the factorial method. Instead of using the caret (^) in multiple places, as in factorial, fibonacci uses a single caret to return the result of the ifTrue:ifFalse: message. That result is the result of either of the two blocks, depending on the value of the message. Test the fibonacci method by evaluating the following message:
#(l 2 3 4 5 6 7 10 20) collect: [:m | m fibonacci ]
The following example illustrates simple pattern matching applied to strings. (Later, you¡ll see how Smalltalk's inheritance allows this same method to do pattern matching for several other classes as well.) Add the method indexOfString: to class String using the Class Hierarchy Browser:
indexOfString: aString "Answer the index position of the first occurrence of aString in the receiver. If no such element is found, answer zero." | index1 index2 limit1 limit2 | limit2 := aString size. limit1 := self size - limit2 + 1. index1 := 1. [index1 <=limit1] whileTrue: [ index2:= 1. [index2 <= limit2 and: [(self at: index1 + index2 - 1) = (aString at: index2)] ] whileTrue: [index2:= index2 + 1]. index2 > limit2 ifTrue: [^index1]. index1 := index1 + 1]. ^0
This method starts at the beginning of the receiver string and searches for the first occurrence of the argument string. It returns either the index of the first character in the receiver's matching substring, or 0 if there are no matches. The method contains two nested whileTrue: loops. The outer loop proceeds through the characters of the receiver. Beginning at each character reached in the outer loop, the inner loop compares the characters of the argument to corresponding characters of the receiver.
Test the indexOfString: method by pattern matching evaluating each of the following messages:
'abcdebcd' indexOfString: 'ebg' 'abcdebcd' indexOfString: 'bcd' 'abcdebcd' indexOfString: 'c' 'abcdebcd' indexOfString: 'abcdebcd' 'abcdebcd' indexOfString: ' '
In Chapter 3 you used a series of expressions to draw a polygon flower in the Turtle Graphics window. The next example packages those expressions into a method, and extends the Graphics Demo program so that you can choose to draw a polygon flower from the demo program's Graphics menu.
To create the new method, add the following method to class GraphicsDemo using the Class Hierarchy Browser:
polyFlower "Draw a polygon flower of size specified by user." | flowerSize length | flowerSize := Prompter prompt: 'Number of sides?' defaultExpression: '30'. graphs removeKey: #polyFlower. self drawBIockNow: [ length := 240 // flowerSize. pen erase; home; north. flowerSize timesRepeat: [ pen up; go: length // 2; down; go: length. flowerSize - 1 timesRepeat: [ pen turn: 360 // flowerSize; go: length] ] ] for. #polyFlower
This method differs from the polygon flower expressions used earlier in two ways. Firstly, the polyFlower method uses a prompter dialog, rather than a constant, to determine the number of the drawing's sides. Secondly, the polyFlower method uses the instance variable pen, rather than the global variable Turtle, to draw the polygon flower. Note also that the now familiar turtle-type graphic code appears in a block passed to drawBlockNow:for: in the polyFlower method. This method implements the segmented drawing mode in which graphics are captured in an "instant replay" format.
To make this new drawing available from within the Graphics Demo, you need to modify the Graphics menu selections. To add Poly Flower as a choice in the demo's Graphics menu, edit the graphicsMenu: method in class GraphicsDemo, as follows (don't forget to adjust the lines: selector to #(6) or the separator line will appear in the wrong place):
graphicsMenu: aPane aPane setMenu: ((Menu labels: 'Poly Flower\Walk Line\Mandala\Multi Mandala\Dragon \Multi Spiral\Variable Size & Aspect \Variable Size\Fixed Size' withCrs lines: #( (6) selectors; #(polyFlower walkLine mandala multiMandala dragon multiSpiral stretch fixedAspect noStretch) ) title: '~Graphics'; owner: self; yourself)
Now try the extended demo program by selecting Graphics Demo from the File menu of the Transcript window. You will find PolyFlower as the new first selection in the Graphics menu of the Graphics Demo window. Notice how your flower redraws itself automatically as you cover and uncover the Graphics Demo window with the Transcript window.
Class variables are global variables accessible to all instances of a class. They are used to share data within a class. Class variables begin with a capital letter.
Let's add a class variable to GraphicsDemo to count the number of times we perform the mandala graphics demo method. First, define the new class variable using the Class Hierarchy Browser. Select GraphicsDemo, then edit the class definition to have the name MandalaCount in the classVariableNames: argument. Then select Save in the File pull down menu or press the Alt + S keys. This creates the class variable and recompiles GraphicsDemo. Now add the following instance method to GraphicsDemo:
MandalaCount ^MandalaCount
Then add the following code at the end of the mandala method in GraphicsDemo:
MandalaCount isNil ifTrue: [MandalaCount := 1] ifFalse: [MandalaCount := MandalaCount + 1]
Remember to add a period after #mandala at the end of the mandala method before you add the statements above. This is needed to maintain proper Smalltalk syntax.
To see how many times mandala has been drawn, evaluate the following expression before and after running the demo program:
GraphicsDemo new mandalaCount
An Inspector is a window which allows you to view and change an object's instance variables. Evaluate the following expression to create an inspector window on an Array:
| a | a := #(1 2 sam 'joe' (4 5)). a at: 2 put: 3 / 4. a inspect
The inspector on an array looks as follows:
Figure 5.2
Inspector
The left pane of the inspector shows the names of the named instance variables and the numbers of the indexed instance variables. You select a name or number in the left pane to see the object contained in that variable in the right pane. You can create an inspector on the contents of an instance variable by clicking on the variable of interest and selecting Inspect from the Inspect pull down menu. Or simply double click on a selected variable to open a new inspector on the variable of interest. Try this by inspecting the second instance variable of the Array which contains a Fraction object, (3/4).
The Fraction object has instance variables named numerator and denominator with values 3 and 4, respectively. Let's try changing the denominator variable. First select denominator, and then go to the right text pane and replace 4 with 100. Then select Save from the File menu. The instance variable is now changed. Just to make sure, close the Fraction inspector window, return to the Array inspector window and select self. There it is; the Fraction has been changed to 3/100.
By the end of this tutorial, you should now be familiar with:
As always, if you need to review any of these topics, you can repeat that section of the tutorial, or refer to a complete description in Part 3 of this manual.
If you are going to exit the Smalltalk/V environment before proceeding to the next tutorial, be sure to save the image.