Skip to main content
Engineering LibreTexts

8.1: Object

  • Page ID
    36372
  • \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \) \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)\(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\) \(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\)\(\newcommand{\AA}{\unicode[.8,0]{x212B}}\)

    For all intents and purposes, Object is the root of the inheritance hierarchy. Actually, in Squeak the true root of the hierarchy is ProtoObject, which is used to define minimal entities that masquerade as objects, but we can ignore this point for the time being.

    Object can be found in the Kernel-Objects category. Astonishingly, there are some 400 methods to be found here (including extensions). In other words, every class that you define will automatically provide these 400 methods, whether you know what they do or not. Note that some of the methods should be removed and new versions of Squeak may remove some of the superfluous methods.

    The class comment for the Object states:

    Object is the root class for almost all of the other classes in the class hierarchy. The exceptions are ProtoObject (the superclass of Object) and its subclasses. Class Object provides default behaviour common to all normal objects, such as access, copying, comparison, error handling, message sending, and reflection. Also utility messages that all objects should respond to are defined here. Object has no instance variables, nor should any be added. This is due to several classes of objects that inherit from Object that have special implementations (SmallInteger and UndefinedObject for example) or the VM knows about and depends on the structure and layout of certain standard classes.

    If we begin to browse the method categories on the instance side of Object we start to see some of the key behaviour it provides.

    Printing

    Every object in Smalltalk can return a printed form of itself. You can select any expression in a workspace and select the print it menu: this executes the expression and asks the returned object to print itself. In fact this sends the message printString to the returned object. The method printString, which is a template method, at its core sends the message printOn: to its receiver. The message printOn: is a hook that can be specialized.

    Object»printOn: is very likely one of the methods that you will most frequently override. This method takes as its argument a Stream on which a String representation of the object will be written. The default implementation simply writes the class name preceded by “a” or “an”. Object»printString returns the String that is written.

    For example, the class Browser does not redefine the method printOn: and sending the message printString to an instance executes the methods defined in Object.

    Browser new printString → 'a Browser'
    

    The class TTCFont shows an example of printOn: specialization. It prints the name of the class followed by the family name, the size and the subfamily name of the font as shown by the code below which prints an instance of the class.

    Code \(\PageIndex{1}\) (Squeak): printOn: Redefinition

    TTCFont»printOn: aStream
        aStream nextPutAll: 'TTCFont(';
        nextPutAll: self familyName; space;
        print: self pointSize; space;
        nextPutAll: self subfamilyName;
        nextPut: $)
    
    TTCFont allInstances anyOne printString → 'TTCFont(BitstreamVeraSans 6 Bold)'
    

    Note that the message printOn: is not the same as storeOn:. The message storeOn: puts on its argument stream an expression that can be used to recreate the receiver. This expression is evaluated when the stream is read using the message readFrom:. printOn: just returns a textual version of the receiver. Of course, it may happen that this textual representation may represent the receiver as a self-evaluating expression.

    A word about representation and self-evaluating representation. In functional programming, expressions return values when executed. In Smalltalk, messages (expressions) return objects (values). Some objects have the nice properties that their value is themselves. For example, the value of the object true is itself i.e., the object true. We call such objects self-evaluating objects. You can see a printed version of an object value when you print the object in a workspace. Here are some examples of such self-evaluating expressions.

    true     →    true
    3@4      →    3@4
    $a       →    $a
    #(123)   →    #(123)
    

    Note that some objects such as arrays are self-evaluating or not depending on the objects they contain. For example, an array of booleans is self-evaluating whereas an array of persons is not. In Squeak 3.9, a mechanism was introduced (via the message isSelfEvaluating) to print collections in their self-evaluating forms as much as possible and this is especially true for brace arrays. The following example shows that a dynamic array is self-evaluating only if its elements are:

    {10@10 . 100@100}            →    {10@10 . 100@100}
    {Browser new . 100@100}      →    an Array(a Browser 100@100)
    

    Remember that literal arrays can only contain literals. Hence the following array does not contain two points but rather six literal elements.

    #(10@10 100@100)    →    #(10 #@ 10 100 #@ 100)
    

    Lots of printOn: method specializations implement self-evaluating behavior. The implementations of Point»printOn: and Interval»printOn: are self-evaluating.

    Code \(\PageIndex{2}\) (Squeak): Self-Evaluation of Point

    Point»printOn: aStream
        "The receiver prints on aStream in terms of infix notation."
        x printOn: aStream.
        aStream nextPut: $@.
        y printOn: aStream
    

    Code \(\PageIndex{3}\) (Squeak): Self-Evaluation of Interval

    Interval»printOn: aStream
        aStream nextPut: $(;
            print: start;
            nextPutAll: ' to: ';
            print: stop.
        step ∼= 1 ifTrue: [aStream nextPutAll: ' by: '; print: step].
        aStream nextPut: $)
    
    1 to: 10 → (1 to: 10) "intervals are self-evaluating"
    

    Identity and equality

    In Smalltalk, the message = tests object equality (i.e., whether two objects represent the same value) whereas == tests object identity (i.e., whether two expressions represent the same object).

    The default implementation of object equality is to test for object identity:

    Code \(\PageIndex{4}\) (Squeak): Object Equality

    Object»= anObject
        "Answer whether the receiver and the argument represent the same object.
        If = is redefined in any subclass, consider also redefining the message hash."
        ↑ self == anObject
    

    This is a method that you will frequently want to override. Consider the case of Complex numbers:

    (1 + 2 i) = (1 + 2 i)     →    true "same value"
    (1 + 2 i) == (1 + 2 i)    →    false "but different objects"
    

    This works because Complex overrides = as follows:

    Code \(\PageIndex{5}\) (Squeak): Equality for Complex Numbers

    Complex»= anObject
        anObject isComplex
            ifTrue: [↑ (real = anObject real) & (imaginary = anObject imaginary)]
            ifFalse: [↑ anObject adaptToComplex: self andSend: #=]
    

    The default implementation of Object»∼= simply negates Object»=, and should not normally need to be changed.

    (1+2i) ∼= (1+4i)     →    true
    

    If you override =, you should consider overriding hash. If instances of your class are ever used as keys in a Dictionary, then you should make sure that instances that are considered to be equal have the same hash value:

    Code \(\PageIndex{6}\) (Squeak): Hash Must Be Reimplemented for Complex Numbers

    Complex»hash
        "Hash is reimplemented because = is implemented."
        ↑ real hash bitXor: imaginary hash.
    

    Although you should override = and hash together, you should never override ==. (The semantics of object identity is the same for all classes.) == is a primitive method of ProtoObject.

    Note that Squeak has some strange behaviour compared to other Smalltalks: for example a symbol and a string can be equal. (We consider this be a bug, not a feature.)

    #'lulu' = 'lulu'    →    true
    'lulu' = #'lulu'    →    true 
    

    Class membership

    Several methods allow you to query the class of an object.

    class. You can ask any object about its class using the message class.

    1 class    →    SmallInteger
    

    Conversely, you can ask if an object is an instance of a specific class:

    1 isMemberOf: SmallInteger    →     true "must be precisely this class"
    1 isMemberOf: Integer         →     false
    1 isMemberOf: Number          →     false
    1 isMemberOf: Object          →     false
    

    Since Smalltalk is written in itself, you can really navigate through its structure using the right combination of superclass and class messages (see Chapter 12).

    isKindOf: Object»isKindOf: answers whether the receiver’s class is either the same as, or a subclass of the argument class.

    1 isKindOf: SmallInteger    → true
    1 isKindOf: Integer         → true
    1 isKindOf: Number          → true
    1 isKindOf: Object          → true
    1 isKindOf: String          → false
    
    1/3 isKindOf: Number        → true
    1/3 isKindOf: Integer       → false
    

    1/3 which is a Fraction is a kind of Number, since the class Number is a superclass of the class Fraction, but 1/3 is not a Integer.

    respondsTo: Object»respondsTo: answers whether the receiver understands the message selector given as an argument.

    1 respondsTo: #,    →    false
    

    Normally it is a bad idea to query an object for its class, or to ask it which messages it understands. Instead of making decisions based on the class of object, you should simply send a message to the object and let it decide (i.e., on the basis of its class) how it should behave.

    Copying

    Copying objects introduces some subtle issues. Since instance variables are accessed by reference, a shallow copy of an object would share its references to instance variables with the original object:

    a1 := { { 'harry' } }.
    a1    →    #(#('harry'))
    a2 := a1 shallowCopy.
    a2    →    #(#('harry'))
    (a1 at: 1) at: 1 put: 'sally'.
    a1    →    #(#('sally'))
    a2    →    #(#('sally')) "the subarray is shared"
    

    Object»shallowCopy is a primitive method that creates a shallow copy of an object. Since a2 is only a shallow copy of a1, the two arrays share a reference to the nested Array that they contain.

    Object»shallowCopy is the “public interface” to Object»copy and should be overridden if instances are unique. This is the case, for example, with the classes Boolean, Character, SmallInteger, Symbol and UndefinedObject.

    Object»copyTwoLevel does the obvious thing when a simple shallow copy does not suffice:

    a1 := { { 'harry' } } .
    a2 := a1 copyTwoLevel.
    (a1 at: 1) at: 1 put: 'sally'.
    a1    →    #(#('sally'))
    a2    →    #(#('harry')) "fully independent state"
    

    Object»deepCopy makes an arbitrarily deep copy of an object.

    a1 := { { { 'harry' } } } .
    a2 := a1 deepCopy.
    (a1 at: 1) at: 1 put: 'sally'.
    a1    →    #(#('sally'))
    a2    →    #(#(#('harry')))
    

    The problem with deepCopy is that it will not terminate when applied to a mutually recursive structure:

    a1 := { 'harry' }.
    a2 := { a1 }.
    a1 at: 1 put: a2.
    a1 deepCopy    →    ... does not terminate!
    

    Although it is possible to override deepCopy to do the right thing, Object»copy offers a better solution:

    Code \(\PageIndex{7}\) (Squeak): Copying Objects as a Template Method

    Object»copy
        "Answer another instance just like the receiver. Subclasses typically override
            postCopy;
        they typically do not override shallowCopy."
        ↑self shallowCopy postCopy
    

    You should override postCopy to copy any instance variables that should not be shared. postCopy should always do a super postCopy.

    Debugging

    The most important method here is halt. In order to set a breakpoint in a method, simply insert the message send self halt at some point in the body of the method. When this message is sent, execution will be interrupted and a debugger will open to this point in your program. (See Chapter 6 for more details about the debugger.)

    The next most important message is assert:, which takes a block as its argument. If the block returns true, execution continues. Otherwise an AssertionFailure exception will be raised. If this exception is not otherwise caught, the debugger will open to this point in the execution. assert: is especially useful to support design by contract. The most typical usage is to check non-trivial pre-conditions to public methods of objects. Stack»pop could easily have been implemented as follows:

    Code \(\PageIndex{8}\) (Squeak): Checking a Pre-Condition

    Stack»pop
        "Return the first element and remove it from the stack."
        self assert: [ self isEmpty not ].
        ↑self linkedList removeFirst element
    

    Do not confuse Object»assert: with TestCase»assert:, which occurs in the SUnit testing framework (see Chapter 7). While the former expects a block as its argument1, the latter expects a Boolean. Although both are useful for debugging, they each serve a very different intent.

    Error handling

    This protocol contains several methods useful for signaling run-time errors.

    Sending self deprecated: anExplanationString signals that the current method should no longer be used, if deprecation has been turned on in the debug protocol of the preference browser. The String argument should offer an alternative.

    1 doIfNotNil: [ :arg | arg printString, ' is not nil' ]
        →    SmallInteger(Object)»doIfNotNil: has been deprecated. use ifNotNilDo:
    

    doesNotUnderstand: is sent whenever message lookup fails. The default implementation, i.e., Object»doesNotUnderstand: will trigger the debugger at this point. It may be useful to override doesNotUnderstand: to provide some other behaviour.

    Object»error and Object»error: are generic methods that can be used to raise exceptions. (Generally it is better to raise your own custom exceptions, so you can distinguish errors arising from your code from those coming from kernel classes.)

    Abstract methods in Smalltalk are implemented by convention with the body self subclassResponsibility. Should an abstract class be instantiated by accident, then calls to abstract methods will result in Object»subclassResponsibility being evaluated.

    Code \(\PageIndex{9}\) (Squeak): Signaling That a Method Is Abstract

    Object»subclassResponsibility
        "This message sets up a framework for the behavior of the class' subclasses.
        Announce that the subclass should have implemented this message."
        self error: 'My subclass should have overridden ', thisContext sender selector
            printString
    

    Magnitude, Number and Boolean are classical examples of abstract classes that we shall see shortly in this chapter.

    Number new + 1    →    Error: My subclass should have overridden #+
    

    self shouldNotImplement is sent by convention to signal that an inherited method is not appropriate for this subclass. This is generally a sign that something is not quite right with the design of the class hierarchy. Due to the limitations of single inheritance, however, sometimes it is very hard to avoid such workarounds.

    A typical example is Collection»remove: which is inherited by Dictionary but flagged as not implemented. (A Dictionary provides removeKey: instead.)

    Testing

    The testing methods have nothing to do with SUnit testing! A testing method is one that lets you ask a question about the state of the receiver and returns a Boolean.

    Numerous testing methods are provided by Object. We have already seen isComplex. Others include isArray, isBoolean, isBlock, isCollection and so on. Generally such methods are to be avoided since querying an object for its class is a form of violation of encapsulation. Instead of testing an object for its class, one should simply send a request and let the object decide how to handle it.

    Nevertheless some of these testing methods are undeniably useful. The most useful are probably ProtoObject»isNil and Object»notNil (though the Null Object2 design pattern can obviate the need for even these methods).

    Initialize release

    A final key method that occurs not in Object but in ProtoObject is initialize.

    Code \(\PageIndex{10}\) (Squeak): initialize as an Empty Hook Method

    ProtoObject»initialize
        "Subclasses should redefine this method to perform initializations on instance
        creation"
    

    The reason this is important is that in Squeak as of version 3.9, the default new method defined for every class in the system will send initialize to newly created instances.

    Code \(\PageIndex{11}\) (Squeak): new as a Class-Side Template Method

    Behavior»new
        "Answer a new initialized instance of the receiver (which is a class) with no
            indexable variables. Fail if the class is indexable."
        ↑ self basicNew initialize
    

    This means that simply by overriding the initialize hook method, new instances of your class will automatically be initialized. The initialize method should normally perform a super initialize to establish the class invariant for any inherited instance variables. (Note that this is not the standard behaviour of other Smalltalks.)


    1. Actually, it will take any argument that understands value, including a Boolean.

    2. Bobby Woolf, Null Object. In Robert Martin, Dirk Riehle and Frank Buschmann, editors, Pattern Languages of Program Design 3. Addison Wesley, 1998.


    This page titled 8.1: Object is shared under a CC BY-SA 3.0 license and was authored, remixed, and/or curated by Andrew P. Black, Stéphane Ducasse, Oscar Nierstrasz, Damien Pollet via source content that was edited to the style and standards of the LibreTexts platform; a detailed edit history is available upon request.