Skip to main content
Engineering LibreTexts

10.1: Object

  • Page ID
    39626
    \( \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 Pharo 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 defines almost 400 methods (in other words, every class that you define will automatically provide all those methods). Note: You can count the number of methods in a class like so:

    Object selectors size "Count the instance methods in Object"
    Object class selectors size "Count the class methods"
    

    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) that the VM knows about and depends on the structure and layout of certain standard classes.

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

    Printing

    Every object can return a printed form of itself. You can select any expression in a textpane and select the Print it menu item: 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.

    Method 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 OpalCompiler does not redefine the method printOn: and sending the message printString to an instance executes the methods defined in Object.

    OpalCompiler new printString
    >>> 'an OpalCompiler'
    

    The class Color shows an example of printOn: specialization. It prints the name of the class followed by the name of the class method used to generate that color.

    Color >> printOn: aStream
        | name |
        (name := self name).
        name = #unnamed
            ifFalse: [
                ^ aStream
                    nextPutAll: 'Color ';
                    nextPutAll: name ].
        self storeOn: aStream]]]
    
    [[[
    Color red printString
    >>> 'Color red'
    

    Note that the message printOn: is not the same as storeOn:. The message storeOn: writes to its argument stream an expression that can be used to recreate the receiver. This expression is executed when the stream is read using the message readFrom:. On the other hand, the message 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 Pharo, messages (expressions) return objects (values). Some objects have the nice property 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 playground. Here are some examples of such self-evaluating expressions.

    true
    >>> true
    3@4
    >>> (3@4)
    $a
    >>> $a
    #(1 2 3)
    >>> #(1 2 3)
    Color red
    >>> Color red
    

    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. The following example shows that a dynamic array is self-evaluating only if its elements are:

    {10@10. 100@100}
    >>> {(10@10). (100@100)}
    {Nautilus new . 100@100}
    >>> an Array(a Nautilus (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.

    Point >> printOn: aStream
        "The receiver prints on aStream in terms of infix notation."
    
        aStream nextPut: $(.
        x printOn: aStream.
        aStream nextPut: $@.
        (y notNil and: [y negative])
            ifTrue: [
                "Avoid ambiguous @- construct"
                aStream space ].
        y printOn: aStream.
        aStream nextPut: $).
    
    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 Pharo, the message = tests object equality (i.e., whether two objects represent the same value) whereas == tests object identity (whether two expressions represent the same object).

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

    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 as defined in the SciSmalltalk/PolyMath packages (PolyMath is a set of packages that offer support for numerical methods):

    (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:

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

    The default implementation of Object>>~= (a test for inequality) simply negates Object>>=, and should not normally need to be changed.

    (1 + 2 i) ~= (1 + 4 i)
    >>> 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:

    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. Message == is a primitive method of ProtoObject.

    Note that Pharo has some strange equality behaviour compared to other Smalltalks. For example a symbol and a string can be equal. (We consider this to 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
    

    isMemberOf:. 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 Pharo is written in itself, you can really navigate through its structure using the right combination of superclass and class messages (see Chapter : Classes and Metaclasses).

    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 an Integer.

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

    1 respondsTo: #,
    >>> false
    

    A note on the usage of respondsTo:. 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 (on the basis of its class) how it should behave. (This concept is sometimes referred to as duck typing).

    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>>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!''!
    

    An alternate solution is to use message copy. It is implemented on Object as follows:

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

    By default postCopy returns self. It means that by default copy is doing the same as shallowCopy but each subclass can decide to customise the postCopy method which acts as a hook. You should override postCopy to copy any instance variables that should not be shared. In addition there is a good chance that postCopy should always do a super postCopy to ensure that state of the superclass is also copied.

    Debugging

    halt. The most important method here is halt. To set a breakpoint in a method, simply insert the expression send self halt at some point in the body of the method. (Note that since halt is defined on Object you can also write 1 halt). When this message is sent, execution will be interrupted and a debugger will open to this point in your program (See Chapter : The Pharo Environment for more details about the debugger.).

    You can also use Halt once or Halt if: aCondition. Have a look at the class Halt which is an special exception.

    assert:. The next most important message is assert:, which expects a block as its argument. If the block evaluates to 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 (Note that this definition is a hypothetical example and not in the Pharo 50 system):

    Stack >> pop
        "Return the first element and remove it from the stack."
    
        self assert: [ self isNotEmpty ].
        ^ self linkedList removeFirst element
    

    Do not confuse Object>>assert: with TestCase>>assert:, which occurs in the SUnit testing framework (see Chapter : SUnit). While the former expects a block as its argument (actually, it will take any argument that understands value, including a Boolean), the latter expects a Boolean. Although both are useful for debugging, they each serve a very different purpose.

    Error handling

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

    deprecated:. Sending self deprecated: signals that the current method should no longer be used, if deprecation has been turned on. You can turn it on/off in the Debugging section using the Settings browser. The argument should describe an alternative. Look for senders of the message deprecated: to get an idea.

    doesNotUnderstand:. doesNotUnderstand: (commonly abbreviated in discussions as DNU or MNU) 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.

    error. 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.)

    subclassResponsibility. Abstract methods 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 executed.

    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 #+''!
    

    shouldNotImplement. 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. There are 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 Object design pattern can obviate the need for even these methods).

    Initialize

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

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

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

    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.


    This page titled 10.1: Object is shared under a CC BY-SA 3.0 license and was authored, remixed, and/or curated by via source content that was edited to the style and standards of the LibreTexts platform; a detailed edit history is available upon request.