Understanding self and super

TL;DR

self and super both represent the reciever of a message. The difference is in the method lookup. A self send is always dynamically resolved while a super send is static.

Introduction

Although all single-inheritance OO languages support some form of self and super mechanisms, they are often not well-understood, so it is worthwhile to take a closer look at them both.

The key point to understand is that both self and super represent the receiver (the object itself), but self resolves message sends dynamically , while super sends are purly static .

Understanding self

Self sends are useful not only for breaking a complex method into helper methods, but also to enable the creation of generic code that can be extended by subclasses.

Consider Collection>>#isEmpty isEmpty "Answer whether the receiver contains any elements." "{} isEmpty >>> true" "{{}} isEmpty >>> false" "'' isEmpty >>> true" "' ' isEmpty >>> false" "(1 to: 10) isEmpty >>> false" "(10 to: 1) isEmpty >>> true" ^self size = 0 .

The #isEmpty method is implemented generically in the abstract class Collection Object subclass: #Collection instanceVariableNames: '' classVariableNames: '' package: 'Collections-Abstract-Base' , as the #size method can be implemented in different ways by its subclasses.

This works because the self send is dynamic — only at run time will it be determined which size method will be performed, since it will depend on the run-time class of the receiver.

Understanding super

Super sends, on the other hand, allow you to extend a method in a subclass, by composing it with the behavior of that same method defined in the superclass.

Consider, for example, SortedCollection>>#sort: sort: aSortBlock "Sort this array using aSortBlock. The block should take two arguments and return true if the first element should preceed the second one." super sort: aSortBlock. sortBlock := aSortBlock .

SortedCollection OrderedCollection subclass: #SortedCollection instanceVariableNames: 'sortBlock' classVariableNames: '' package: 'Collections-Sequenceable-Ordered' overrides the #sort: method it inherits from OrderedCollection SequenceableCollection subclass: #OrderedCollection instanceVariableNames: 'array firstIndex lastIndex' classVariableNames: '' package: 'Collections-Sequenceable-Ordered' , without changing the core logic, but additionally saving the sortBlock in a private slot. Since it overrides #sort:, the only way to avoid duplicating its code is to invoke it using a super send.

So much is clear. What is perhaps less obvious is that the super send is purely static . The sort: method to perform is statically known when this code is compiled. It will always refer to OrderedCollection>>#sort: sort: aSortBlock "Sort this array using aSortBlock. The block should take two arguments and return true if the first element should preceed the second one." self size <= 1 ifTrue: [^ self]. "nothing to do" array mergeSortFrom: firstIndex to: lastIndex src: array shallowCopy dst: array by: aSortBlock , even in any subclasses that might be defined later.

Using both self and super

We can see the difference clearly in the implementation of Symbol>>#= = aSymbol "Compare the receiver and aSymbol." self == aSymbol ifTrue: [^ true]. self class == aSymbol class ifTrue: [^ false]. "Use String comparison otherwise" ^ super = aSymbol .

A symbol equals another object either (i) if they are identical (self == ...), or (ii) if they are of the same class (self class == ...), and they have the same values as Strings.

While the self sends are dynamic, the super send refers statically to the method String>>#= = aString "Answer whether the receiver sorts equally as aString. The collation order is simple ascii (with case differences)." " 'abc' = 'def' >>> false" " 'abc' = 'abc' >>> true" " 'def' = 'abc' >>> false" (aString isString and: [self size = aString size]) ifFalse: [^false]. ^ (self compare: self with: aString collated: AsciiOrder) = 2 inherited from the superclass, String ArrayedCollection subclass: #String instanceVariableNames: '' classVariableNames: 'AsciiOrder CSLineEnders CSNonSeparators CSSeparators CaseInsensitiveOrder CaseSensitiveOrder LowercasingTable Tokenish TypeTable UppercasingTable' package: 'Collections-Strings-Base' .

Never use super to perform a different method

Super sends are there only to extend an inherited method, and provide a way to access the overridden implementation. They should not be used to hardwire other methods. Instead a self send should always be used instead.

The reason is clear — by statically hardwiring the method to be performed, you prevent subclasses from plugging in a different implementation. Whether the helper method is implemented in the class or not, it should be performed by a self send.

Caveat. Of course there are exceptions to this rule, but they tend to be unusual and even questionable.

Consider, for example, SortedCollection>>#add: add: newObject ^ super insert: newObject before: (self indexForInserting: newObject) .

Why is this a super send? Well, it turns out that SortedCollection OrderedCollection subclass: #SortedCollection instanceVariableNames: 'sortBlock' classVariableNames: '' package: 'Collections-Sequenceable-Ordered' removes the inherited method for SortedCollection>>#insert:before: insert: anObject before: spot self shouldNotImplement because that method would conflict with its responsibility to maintain the order of its elements.

Of course that begs the question whether SortedCollection should inherit from OrderedCollection in the first place, if it does not fulfil the same responsibilities. Perhaps the hierarchy should be refactored to avoid the ugly removal of inherited methods.

Here's another curious example: Collection>>#printNameOn: printNameOn: aStream super printOn: aStream

Why isn't this a self send? Well, it's because Collection Object subclass: #Collection instanceVariableNames: '' classVariableNames: '' package: 'Collections-Abstract-Base' introduces a more intelligent #printOn: method, but the old method should still be available under a new name: Collection>>#printOn: printOn: aStream "Append a sequence of characters that identify the receiver to aStream." self printNameOn: aStream. self printElementsOn: aStream

So, as a general rule, you should only use a super send to extend the same method, but there may be some curious situations that justify breaking this rule.

A puzzle

What is the result of evaluating this code?

 self == super
  

Is there any situation in which you might expect a different result?