6.8: Method Lookup Follows the Inheritance Chain
- Page ID
- 40126
\( \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}\)What exactly happens when an object receives a message? This is a two step process: method lookup and method execution.
Lookup. First, the method having the same name as the message is looked up.
Method Execution. Second, the found method is applied to the receiver with the message arguments: When the method is found, the arguments are bound to the parameters of the method, and the virtual machine executes it.
The lookup process is quite simple:
- The class of the receiver looks up the method to use to handle the message.
- If this class does not have that method method defined, it asks its superclass, and so on, up the inheritance chain.
It is essentially as simple as that. Nevertheless there are a few questions that need some care to answer:
- What happens when a method does not explicitly return a value?
- What happens when a class reimplements a superclass method?
- What is the difference between
self
andsuper
sends? - What happens when no method is found?
The rules for method lookup that we present here are conceptual; virtual machine implementors use all kinds of tricks and optimizations to speed up method lookup. That’s their job, but you should never be able to detect that they are doing something different from our rules.
First let us look at the basic lookup strategy, and then consider these further questions.
Method lookup
Suppose we create an instance of EllipseMorph
.
anEllipse := EllipseMorph new.
If we now send this object the message defaultColor
, we get the result Color yellow
.
anEllipse defaultColor >>> Color yellow
The class EllipseMorph
implements defaultColor
, so the appropriate method is found immediately.
EllipseMorph >> defaultColor "Answer the default color/fill style for the receiver" ^ Color yellow

In contrast, if we send the message openInWorld
to anEllipse
, the method is not immediately found, since the class EllipseMorph
does not implement openInWorld
. The search therefore continues in the superclass, BorderedMorph
, and so on, until an openInWorld
method is found in the class Morph
(see Figure \(\PageIndex{1}\)).
Morph >> openInWorld "Add this morph to the world." self openInWorld: self currentWorld
Returning self
Notice that EllipseMorph>>defaultColor
explicitly returns Color yellow
, whereas Morph>>openInWorld
does not appear to return anything.
Actually a method always answers a message with a value (which is, of course, an object). The answer may be defined by the ^ construct in the method, but if execution reaches the end of the method without executing a ^, the method still answers a value – it answers the object that received the message. We usually say that the method answers self, because in Pharo the pseudo-variable self
represents the receiver of the message, much like the keyword this
in Java. Other languages, such as Ruby, by default return the value of the last statement in the method. Again, this is not the case in Pharo, instead you can imagine that a method without an explicit return ends with ^ self.
Important
self
represents the receiver of the message.
This suggests that openInWorld
is equivalent to openInWorldReturnSelf
, defined below.
Morph >> openInWorld "Add this morph to the world." self openInWorld: self currentWorld ^ self
Why is explicitly writing ^ self
not a so good thing to do? When you return something explicitly, you are communicating that you are returning something of interest to the sender. When you explicitly return self
, you are saying that you expect the sender to use the returned value. This is not the case here, so it is best not to explicitly return self
. We only return self
on special case to stress that the receiver is returned.
This is a common idiom in Pharo, which Kent Beck refers to as Interesting return value:
”Return a value only when you intend for the sender to use the value.”
Important
By default (if not specified differently) a method returns the message receiver.
Overriding and extension
If we look again at the EllipseMorph
class hierarchy in Figure \(\PageIndex{1}\), we see that the classes Morph
and EllipseMorph
both implement defaultColor
. In fact, if we open a new morph (Morph new openInWorld
) we see that we get a blue morph, whereas an ellipse will be yellow by default.
We say that EllipseMorph
overrides the defaultColor
method that it inherits from Morph
. The inherited method no longer exists from the point of view of anEllipse
.
Sometimes we do not want to override inherited methods, but rather extend them with some new functionality, that is, we would like to be able to invoke the overridden method in addition to the new functionality we are defining in the subclass. In Pharo, as in many object-oriented languages that support single inheritance, this can be done with the help of super
sends.
A frequent application of this mechanism is in the initialize
method. Whenever a new instance of a class is initialized, it is critical to also initialize any inherited instance
variables. However, the knowledge of how to do this is already captured in the initialize
methods of each of the superclass in the inheritance chain. The subclass has no business even trying to initialize
inherited instance variables!
It is therefore good practice whenever implementing an initialize
method to send super initialize
before performing any further initialization:
BorderedMorph >> initialize "initialize the state of the receiver" super initialize. self borderInitialize
We need super
sends to compose inherited behaviour that would otherwise be overridden.
Important
It is a good practice that an initialize
method start by sending super initialize
.
Self sends and super sends
self
represents the receiver of the message and the lookup of the method starts in the class of the receiver. Now what is super
? super
is not the superclass! It is a common and natural mistake to think this. It is also a mistake to think that lookup starts in the superclass of the class of the receiver.
Important
self
represents the receiver of the message and the method lookup starts in the class of the receiver.
How do self sends differ from super sends?
Like self
, super
represents the receiver of the message. Yes you read it well! The only thing that changes is the method lookup. Instead of lookup starting in the class of the receiver, it starts in the superclass of the class of the method where the super
send occurs.
Important
super
represents the receiver of the message and the method lookup starts in the superclass of the class of the method where the super
send occurs.
We shall see with the following example precisely how this works. Imagine that we define the following three methods:
First we define the method fullPrintOn:
on class Morph
that just adds to the stream the name of the class followed by the string ’ new’ - the idea is that we could execute the resulting string and gets back an instance similar to the receiver.
Morph >> fullPrintOn: aStream aStream nextPutAll: self class name, ' new'
Second we define the method constructorString
that send the message fullPrintOn:
.
Morph >> constructorString ^ String streamContents: [ :s | self fullPrintOn: s ].
Finally, we define the method fullPrintOn:
on the class BorderedMorph
superclass of EllipseMorph
. This new method extends the superclass behavior: it invokes it and adds extra behavior.
BorderedMorph >> fullPrintOn: aStream aStream nextPutAll: '('. super fullPrintOn: aStream. aStream nextPutAll: ') setBorderWidth: '; print: borderWidth; nextPutAll: ' borderColor: ', (self colorString: borderColor)
Consider the message constructorString
sent to an instance of EllipseMorph
:
EllipseMorph new constructorString >>> '(EllipseMorph new) setBorderWidth: 1 borderColor: Color black'
How exactly is this result obtained through a combination of self
and super
sends? First, anEllipse constructorString
will cause the method constructorString
to be found in the class Morph
, as shown in Figure \(\PageIndex{2}\).

The method Morph>>constructorString
performs a self send of fullPrintOn:
. The message fullPrintOn:
is looked up starting in the class EllipseMorph
, and the method BorderedMorph>>fullPrintOn:
is found in BorderedMorph
(see Figure \(\PageIndex{2}\)). What is critical to notice is that the self
send causes the method lookup to start again in the class of the receiver, namely the class of anEllipse
.
At this point, BorderedMorph>>fullPrintOn:
does a super
send to extend the fullPrintOn: behaviour
it inherits from its superclass. Because this is a super
send, the lookup now starts in the superclass of the class where the super
send occurs, namely in Morph
. We then immediately find and evaluate Morph>>fullPrintOn:
.
Stepping back
A self
send is dynamic in the sense that by looking at the method containing it, we cannot predict which method will be executed. Indeed an instance of a subclass may receive the message containing the self expression and redefine the method in that subclass. Here EllipseMorph
could redefine the method fullPrintOn:
and this method would be executed by method constructorString
. Note that by only looking at the method constructorString
, we cannot predict which fullPrintOn:
method (either the one of EllipseMorph
, BorderedMorph
, or Morph
) will be executed when executing the method constructorString
, since it depends on the receiver the constructorString
message.
Important
A self
send triggers a method lookup starting in the class of the receiver. A self
send is dynamic in the sense that by looking at the method containing it, we cannot predict which method will be executed.
Note that the super
lookup did not start in the superclass of the receiver. This would have caused lookup to start from BorderedMorph
, resulting in an infinite loop!
If you think carefully about super
send and Figure \(\PageIndex{2}\), you will realize that super
bindings are static: all that matters is the class in which the text of the super
send is found. By contrast, the meaning of self
is dynamic: it always represents the receiver of the currently executing message. This means that all messages sent to self
are looked up by starting in the receiver’s class.
Important
A super
send triggers a method lookup starting in the superclass of the class of the method performing the super
send. We say that super
sends are static because just looking at the method we know the class where the lookup should start (the class above the class containing the method).
Message not understood
What happens if the method we are looking for is not found?
Suppose we send the message foo
to our ellipse. First the normal method lookup would go through the inheritance chain all the way up to Object
(or rather ProtoObject
) looking for this method. When this method is not found, the virtual machine will cause the object to send self doesNotUnderstand: #foo
. (See Figure \(\PageIndex{3}\).)

Now, this is a perfectly ordinary, dynamic message send, so the lookup starts again from the class EllipseMorph
, but this time searching for the method doesNotUnderstand:
. As it turns out, Object
implements doesNotUnderstand:
. This method will create a new MessageNotUnderstood
object which is capable of starting a Debugger
in the current execution context.
Why do we take this convoluted path to handle such an obvious error? Well, this offers developers an easy way to intercept such errors and take alternative action. One could easily override the method Object>>doesNotUnderstand:
in any subclass of Object
and provide a different way of handling the error.
In fact, this can be an easy way to implement automatic delegation of messages from one object to another. A Delegator
object could simply delegate all messages it does not understand to another object whose responsibility it is to handle them, or raise an error itself!