Smalltalk80LanguageImplementation:Chapter 14

From 흡혈양파의 번역工房
Revision as of 09:29, 9 May 2014 by Onionmixer (talk | contribs) (정오표 수정)
Jump to navigation Jump to search
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

ll 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.


Notes