Smalltalk80LanguageImplementation:Chapter 14
- Chapter 14 Kernel Support
Kernel Support
Object
Magnitude
Character
Date
Time
Number
Float
Fraction
Integer
LargeNegativeInteger
LargePositiveInteger
SmallInteger
LookupKey
Association
Link
Process
Collection
SequenceableCollection
LinkedList
Semaphore
ArrayedCollection
Array
Bitmap
DisplayBitmap
RunArray
String
Symbol
Text
ByteArray
Interval
OrderedCollection
SortedCollection
Bag
MappedCollection
Set
Dictionary
IdentifyDictionary
Stream
PositionableStream
ReadStream
WriteStream
ReadWriteStream
ExternalStream
FileStream
Random
File
FileDirectory
FilePage
UndefinedObject***
Boolean***
False***
True***
ProcessorScheduler
Delay
SharedQueue
Behavior
ClassDescription
Class
MetaClass
Point
Rectangle
BitBit
CharacterScanner
Pen
DisplayObject
DisplayMedium
Form
Cursor
DisplayScreen
InfiniteForm
OpaqueForm
Path
Arc
Circle
Curve
Line
LinearFit
Spline
Class UndefinedObject
The object nil represents a value for uninitialized variables. It also represents meaningless results. It is the only instance of class UndefinedObject.
The purpose of including class UndefinedObject in the system is to handle error messages.The typical error in evaluating Smal]talk-80 expressions is that some object is sent a message it does not understand. Often this occurs because a variable is not properly initialized--in many cases, the variable name that should refer to some other object refers to nil instead. The error message is of the form
className does not understand messageSelector
where className mentions the class of the receiver and messageSelector is the selector of the erroneously-sent message.
Note, if nil were an instance of Object, then a message sent to it in error would state
Object does not understand messageSelector
which is less explicit than stating that an undefined object does not understand the message. At the price of a class description, it was possible to improve on the error message.
Tests to see if an object is nil are handled in class Object, but reimplemented in UndefinedObject. In class Object, messages isNil and notNil are implemented as
isNil
↑false
notNil
↑true
In class UndefinedObject, messages isNil and notNil are implemented as
isNil
↑true
notNil
↑false
so that no conditional test in Object is required.
Classes Boolean, True, and False
Protocol for logical values is provided by the class Boolean; logical values are represented by subclasses of Boolean~True and False. The subclasses add no new protocol; they reimplement many messages to have better performance than the methods in the superclass. The idea is similar to that in testing for nil in Object and UndefinedObject; true knows that it represents logical truth and false knows that it represents logical falsehood. We show the implementation of some of the controlling protocol to illustrate this idea.
The logical operations are
logical operations | |
& aBoolean | Evaluating conjunction. Answer true if both the receiver and the argument are true. |
| aBoolean | Evaluating disjunction. Answer true if either the receiver or the argument is true. |
not | Negation. Answer true if the receiver is false, answer false if the receiver is true. |
eqv: aBoolean | Answer true if the receiver is equivalent to the argument, aBoolean. |
xor: aBoolean | Exclusive OR. Answer true if the receiver is not equivalent to aBoolean. |
Boolean instance protocol |
These conjunction and disjunction operations are "evaluating" --this means that the argument is evaluated regardless of the value of the receiver. This is in contrast to and: and or: in which the receiver determines whether to evaluate the argument.
controlling | |
and: alternativeBlock | Nonevaluating conjunction. If the receiver is true, answer the value of the argument; otherwise, answer false without evaluating the argument. |
or: alternativeBlock | Nonevaluating disjunction. If the receiver is false, answer the value of the argument; otherwise, answer true without evaluating the argument. |
ifTrue: trueAlternativeBlock ifFalse: falseAlternativeBlock | Conditional statement. If the receiver is true, answer the result of evaluating trueAlternativeBlock; otherwise answer the result of evaluating falseAlternativeBlock. |
ifFalse: falseAlternativeBlock ifTrue: trueAlternativeBlock | Conditional statement. If the receiver is true, answer the result of evaluating trueAlternativeBlock; otherwise answer the result of evaluating falseAlternativeBlock. |
ifTrue: trueAlternativeBlock | Conditional statement. If the receiver is true, answer the result of evaluating trueAlternativeBlock; otherwise answer nil. |
ifFalse: falseAlternativeBlock | Conditional statement. If the receiver is false, answer the result of evaluating falseAlternativeBlock; otherwise answer nil. |
Boolean instance protocol |
The arguments to and: and or: must be blocks in order to defer evaluation. Conditional statements are provided as messages ifTrue:ifFalse:, ifFalse:ifTrue:, ifTrue:, and ifFalse:, as already specified and exemplified throughout the previous chapters. The messages are implemented in the subclasses of class Boolean so that the appropriate argument block is evaluated.
In class True, the methods are
ifTrue: trueAlternativeBlock ifFalse: falseAlternativeBlock
↑trueAlternativeBlock value
ifFalse: falseAlternativeBlock ifTrue: trueAlternativeBlock
↑trueAlternativeBlock value
ifTrue: trueAlternativeBlock
↑trueAlternativeBlock value
ifFalse: falseAlternativeBlock
↑nil
In class False, the methods are
ifTrue: trueAlternativeBlock ifFalse: falseAlternativeBlock
↑falseAlternativeBlock value
ifFalse: falseAlternativeBlock ifTrue: trueAlternativeBlock
↑falseAlternativeBlock value
ifTrue: trueAlternativeBlock
↑nil
ifFalse: falseAlternativeBlock
↑falseAlternativeBlock value
If x is 3, then
x > 0 ifTrue: [x ← X - 1] ifFalse: [x ← x + 1]
is interpreted as x > 0 evaluates to true, the sole instance of class True; the method for ifTrue:ifFalse: is found in class True, so the block [x ← x - 1] is evaluated without further testing.
In this way, the message lookup mechanism provides an effective implementation of conditional control with no additional primitive operations or circular definitions.
Additional Protocol for Class Object
Protocol for class Object, shared by all objects, was introduced in Chapter 6. Several categories of messages were not included in that early discussion. Most of these are part of Object's protocol to provide system support for message handling, dependence relationships, primitive message handling, and system primitives.
Dependence Relationships Among Objects
Information in the Smalltalk-80 system is represented by objects. The variables of objects themselves refer to objects; in this sense, objects are explicitly related or dependent on one another. Classes are related to their superclasses and metaclasses; these classes share external and internal descriptions and are thereby dependent on one another. These forms of dependency are central to the semantics of the Smalltalk-80 language. They coordinate descriptive information among objects.
An additional kind of dependency is supported in class Object. Its purpose is to coordinateactivities among different objects. Specifically, its purpose is to be able to link one object, say A, to one or more other objects, say B, so B can be informed if A changes in any way. Upon being informed when A changes and the nature of the change, B can decide to take some action such as updating its own status. The concept of changeand update,therefore, are integral to the support of this third kind of object dependence relationship.
The protocol in class Object is
dependents access | |
addDependent: anObject | Add the argument, anObject, as one of the receiver's dependents. |
removeDependent: anObject | Remove the argument, anObject, as one of the receiver's dependents. |
dependents | Answer an OrderedCollection of the objects that are dependent on the receiver, that is, the objects that should be notified if the receiver changes. |
release | Remove references to objects that may refer back to the receiver. This message is reimplemented by any subclass that creates references to dependents; the expression super release is included in any such reimplementation. |
change and update | |
changed | The receiver changed in some general way; inform all the dependents by sending each dependent an update: message. |
changed: aParameter | The receiver changed; the change is denoted by the argument, aParameter. Usually the argument is a Symbol that is part of the dependent's change protocol; the default behavior is to use the receiver itself as the argument. Inform all of the dependents. |
update: aParameter | An object on whom the receiver is dependent has changed. The receiver updates its status accordingly (the default behavior is to do nothing). |
broadcast: aSymbol | Send the argument, aSymbol, as a unary message to all of the receiver's dependents. |
broadcast: aSymbol with: anObject | Send the argument, aSymbol, as a keyword message with argument, anObject, to all of the receiver's dependents. |
Object instance protocol |
Take, as an example, the objects that model traffic lights. A typical traffic light at a street corner is an object with three lights, each a different color. Only one of these lights can be ON at a given moment. In this sense, the ON-OFF status of each of the three lights is dependent on the status of the other two. There are a number of ways to create this relationship. Suppose we create the class Light as follows.
class name | Light |
superclass | Object |
instance variable names | status |
class methods | instance creation
setOn
↑self new setOn
setOff
↑self new setOff
|
instance methods | status
turnOn
self isOff
ifTrue: [status ← true. self changed]
turnOff
self isOn
ifTrue: [status ← false]
testing
isOn
↑status
isOff
↑status not
change and update
update: aLight
aLight = = self ifFalse: [self turnOff]
private
setOn
status ← true
setOff
status ← false
|
The model is very simple. A Light is either on or off, so a status flag is kept as an instance variable; it is true if the Light is on, false if the Light is off. Whenever a Light is turned on (turnOn), it sends itself the changed message. Any other status change is not broadcast to the dependents on the assumption that a Light is turned off in reaction to turning on another Light. The default response to changed is to send all dependents the message update: self (i.e., the object that changed is the argument to the update: message). Then update: is implemented in Light to mean turn off. If the parameter is the receiver, then, of course, the update: is ignored.
The class TrafficLight is defined to set up any number of coordinated lights. The instance creation message with: takes as its argument the number of Lights to be created. Each Light is dependent on all other Lights. When the TrafficLight is demolished, the dependencies among its Lights are disconnected (the message inherited from class Object for disconnecting dependents is release; it is implemented in TrafficLight in order to broadcast the message to all Lights).
class name | TrafficLight |
superclass | Object |
instance variable names | lights |
class methods | instance creation
with: numberOfLights
↑self new lights: numberOfLights
|
instance methods | operate
turnOn: lightNumber
(lights at: lightNumber) turnOn
initialize-release
release
super release.
lights do: [ :eachLight | eachLight release].
lights ← nil
private
lights: numberOfLights
lights ← Array new: (numberOfLights max: 1).
lights at: 1 put: Light setOn.
2 to: numberOfLights do:
[ :index | lights at: index put: Light setOff].
lights do:
[ :eachLight |
lights do:
[ :dependentLight |
eachLight ~~ dependentLight
ifTrue: [eachLight addDependent: dependentLight]]]
|
The private initialization method is lights: numberOfLights. Each light is created turned off except for the first light. Then each light is connected to all the other lights (using the message addDependent:). The simulated TrafficLight operates by some round robin, perhaps timed, sequencing through each light, turning it on. A simple example shown below creates the TrafficLight with the first light on, and then turns on each of the other lights, one at a time. A simulation of a traffic corner might include different models for controlling the lights.
trafficLight ← TrafficLight with: 3.
trafficLight turnOn: 2.
trafficLight turnOn: 3
The message turnOn: to a TrafficLight sends the message turnOn to the designated Light. If the Light is currently off, then it is set on and the message changed sent. The message changed sends update: to each dependent Light; if a dependent light is on, it is turned off.
A particularly important use of this dependency protocol is to support having multiple graphical images of an object. Each image is dependent on the object in the sense that, if the object changes, the image must be informed so that it can decide whether the change affects the displayed information. The user interface to the Smalltalk-80 system makes liberal use of this support for broadcasting notices that an object has changed; this is used to coordinate the contents of a sequence of menus of possible actions that the user can take with respect to the contents of information displayed on the screen. Menus themselves can be created by linking possible actions together, in a way similar to the way we linked together the traffic lights.
Message Handling
All processing in the Smalltalk-80 system is carried out by sending messages to objects. For reasons of efficiency, instances of class Message are only created when an error occurs and the message state must be stored in an accessible structure. Most messages in the system, therefore, do not take the form of directly creating an instance of Message and transmitting it to an object.
In some circumstances, it is useful to compute the message selector of a message transmission. For example, suppose that a list of possible message selectors is kept by an object and, based on a computation, one of these selectors is chosen. Suppose it is assigned as a value of a variable selector. Now we wish to transmit the message to some object, say, to receiver. We can not simply write the expression as
receiver selector
because this means--send the object referred to by receiver the unary message selector. We could, however, write
receiver perform: selector
The result is to transmit the value of the argument, selector, as the message to receiver. Protocol to support this ability to send a computed message to an object is provided in class Object. This protocol includes methods for transmitting computed keyword as well as unary messages.
message handling | |
perform: aSymbol | Send the receiver the unary message indicated by the argument, aSymbol.The argument is the selector of the message. Report an error if the number of arguments expected by the selector is not zero. |
perform:aSymbol with: anObject | Send the receiver the keyword message indicated by the arguments. The first argument, aSymbol, is the selector of the message. The other argument, anObject, is the argument of the message to be sent. Report an error if the number of arguments expected by the selector is not one. |
perform: aSymbol with: firstObject with: secondObject | Send the receiver the keyword message indicated by the arguments. The first argument, aSymbol, is the selector of the message. The other arguments, firstObject and secondObject, are the arguments of the message to be sent. Report an error if the number of arguments expected by the selector is not two. |
perform: aSymbol with: firstObject with: secondObject with: thirdObject | Send the receiver the keyword message indicated by the arguments. The first argument, aSymbol, is the selector of the message. The other arguments, firstObject, secondObject, and thirdObject, are the arguments of the message to be sent. Report an error if the number of arguments expected by the selector is not three. |
perform: selector withArguments: anArray | Send the receiver the keyword message indicated by the arguments. The argument, selector, is the selector of the message. The arguments of the message are the elements of anArray. Report an error if the number of arguments expected by the selector is not the same as the size of anArray. |
Object instance protocol |
One way in which this protocol can be used is as a decoder of user commands. Suppose for example that we want to model a very simple calculator in which operands precede operators. A possible implementation represents the calculator as having (1) the current result, which is also the first operand, and (2) a possibly undefined second operand. Each operator is a message selector understood by the result. Sending the message clear, once, resets the operand; sending the message clear when the operand is reset will reset the result.
class name | Calculator |
superclass | Object |
instance variable names | result operand |
class methods | instance creation
new
↑super new initialize
|
instance methods | accessing
result
↑result
calculating
apply: operator
(self respondsTo: operator)
ifFalse: [self error: 'operation not understood'].
operand isNil
ifTrue: [result ← result perform: operator]
ifFalse: [result ← result perform: operator with: operand]
clear
operand isNil
ifTrue: [result ← 0]
ifFalse; [operand ← nil]
operand: aNumber
operand ← aNumber
private
initialize
result ← 0
|
An example illustrates the use of the class Calculator.
hp ← Calculator new
Create hp as a Calculator. The instance variables are initialized with result 0 and operand nil.
hp operand: 3
Imagine the user has pressed the key labeled 3 and set the operand.
hp apply: #+
The user selects addition. The method for apply determines that the operator is understood and that the operand is not nil; therefore, the result is set by the expression
result perform: operator with: operand
which is equivalent to
0 + 3
The methods sets result to 3; operand remains 3 so that
hp apply: #+
again adds 3, so the result is now 6.
hp operand: 1.
hp apply: #-.
hp clear.
hp apply: #squared
The result was 6, subtract 1, and compute the square; result is now 25.
System Primitive Messages
There are a number of messages specified in class Object whose purpose is to support the needs of the overall system implementation. They are categorized as system primitives. These are messages that provide direct access to the state of an instance and, to some extent, violate the principle that each object has sovereign control over storing values into its variables. However, this access is needed by the language interpreter. It is useful in providing class description/development utilities for the programming environment. Examples of these messages are instVarAt: anInteger and instVarAt: anInteger put: anObject which retrieve and store the values of named instance variables, respectively.
system primitives | |
become: otherObject | Swap the instance pointers of the receiver and the argument, otherObject. All variables in the entire system that pointed to the receiver will now point to the argument and vice versa. Report an error if either object is a SmallInteger. |
instVarAt: index | Answer a named variable in the receiver. The numbering of the variables corresponds to the order in which the named instance variables were defined. |
instVarAt: index put: value | Store the argument, value, into a named variable in the receiver. The number of variables corresponds to the order in which the named instance variables were defined. Answer value. |
nextInstance | Answer the next instance after the receiver in the enumeration of all instances of this class. Answer nil if all instances have been enumerated. |
numberOfPointers | Answer the number of objects to which the receiver refers. |
refct | Answer the number of object pointers in the system that point at the receiver. Answer O if the receiver is a SmallInteger. |
Object instance protocol |
Probably the most unusual and effective of the system primitive messages is the message become: otherObject. The response to this message is to swap the instance pointer of the receiver with that of the argument, otherObject. An example of the use of this message is found in the implementation of the message grow in several of the collection classes. The message grow is sent when the number of elements that can be stored in a (fixed-length) collection have to be increased without copyingthe collection; copying is undesirable because all shared references to the collection must be preserved. Thus a new collection is created, its elements stored, and then the original collection transforms into (becomes) the new one. All pointers to the original collection are replaced by pointers to the new one.
The following is the method for grow as specified in class SequenceableCollection.
grow
| newCollection |
newCollection ← self species new: self size + self growSize.
newCollection replaceFrom: 1 to: self size with: self.
↑self become: newCollection
growSize
↑10
Subclasses can redefine the response to the message growSize in order to specify alternative numbers of elements by which to expand.