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}}\)
\( \newcommand{\vectorA}[1]{\vec{#1}} % arrow\)
\( \newcommand{\vectorAt}[1]{\vec{\text{#1}}} % arrow\)
\( \newcommand{\vectorB}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vectorC}[1]{\textbf{#1}} \)
\( \newcommand{\vectorD}[1]{\overrightarrow{#1}} \)
\( \newcommand{\vectorDt}[1]{\overrightarrow{\text{#1}}} \)
\( \newcommand{\vectE}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash{\mathbf {#1}}}} \)
\( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)
\(\newcommand{\avec}{\mathbf a}\) \(\newcommand{\bvec}{\mathbf b}\) \(\newcommand{\cvec}{\mathbf c}\) \(\newcommand{\dvec}{\mathbf d}\) \(\newcommand{\dtil}{\widetilde{\mathbf d}}\) \(\newcommand{\evec}{\mathbf e}\) \(\newcommand{\fvec}{\mathbf f}\) \(\newcommand{\nvec}{\mathbf n}\) \(\newcommand{\pvec}{\mathbf p}\) \(\newcommand{\qvec}{\mathbf q}\) \(\newcommand{\svec}{\mathbf s}\) \(\newcommand{\tvec}{\mathbf t}\) \(\newcommand{\uvec}{\mathbf u}\) \(\newcommand{\vvec}{\mathbf v}\) \(\newcommand{\wvec}{\mathbf w}\) \(\newcommand{\xvec}{\mathbf x}\) \(\newcommand{\yvec}{\mathbf y}\) \(\newcommand{\zvec}{\mathbf z}\) \(\newcommand{\rvec}{\mathbf r}\) \(\newcommand{\mvec}{\mathbf m}\) \(\newcommand{\zerovec}{\mathbf 0}\) \(\newcommand{\onevec}{\mathbf 1}\) \(\newcommand{\real}{\mathbb R}\) \(\newcommand{\twovec}[2]{\left[\begin{array}{r}#1 \\ #2 \end{array}\right]}\) \(\newcommand{\ctwovec}[2]{\left[\begin{array}{c}#1 \\ #2 \end{array}\right]}\) \(\newcommand{\threevec}[3]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \end{array}\right]}\) \(\newcommand{\cthreevec}[3]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \end{array}\right]}\) \(\newcommand{\fourvec}[4]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \\ #4 \end{array}\right]}\) \(\newcommand{\cfourvec}[4]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \\ #4 \end{array}\right]}\) \(\newcommand{\fivevec}[5]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \\ #4 \\ #5 \\ \end{array}\right]}\) \(\newcommand{\cfivevec}[5]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \\ #4 \\ #5 \\ \end{array}\right]}\) \(\newcommand{\mattwo}[4]{\left[\begin{array}{rr}#1 \amp #2 \\ #3 \amp #4 \\ \end{array}\right]}\) \(\newcommand{\laspan}[1]{\text{Span}\{#1\}}\) \(\newcommand{\bcal}{\cal B}\) \(\newcommand{\ccal}{\cal C}\) \(\newcommand{\scal}{\cal S}\) \(\newcommand{\wcal}{\cal W}\) \(\newcommand{\ecal}{\cal E}\) \(\newcommand{\coords}[2]{\left\{#1\right\}_{#2}}\) \(\newcommand{\gray}[1]{\color{gray}{#1}}\) \(\newcommand{\lgray}[1]{\color{lightgray}{#1}}\) \(\newcommand{\rank}{\operatorname{rank}}\) \(\newcommand{\row}{\text{Row}}\) \(\newcommand{\col}{\text{Col}}\) \(\renewcommand{\row}{\text{Row}}\) \(\newcommand{\nul}{\text{Nul}}\) \(\newcommand{\var}{\text{Var}}\) \(\newcommand{\corr}{\text{corr}}\) \(\newcommand{\len}[1]{\left|#1\right|}\) \(\newcommand{\bbar}{\overline{\bvec}}\) \(\newcommand{\bhat}{\widehat{\bvec}}\) \(\newcommand{\bperp}{\bvec^\perp}\) \(\newcommand{\xhat}{\widehat{\xvec}}\) \(\newcommand{\vhat}{\widehat{\vvec}}\) \(\newcommand{\uhat}{\widehat{\uvec}}\) \(\newcommand{\what}{\widehat{\wvec}}\) \(\newcommand{\Sighat}{\widehat{\Sigma}}\) \(\newcommand{\lt}{<}\) \(\newcommand{\gt}{>}\) \(\newcommand{\amp}{&}\) \(\definecolor{fillinmathshade}{gray}{0.9}\)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.