Polymorphism, Method Lookup & doesNotUnderstand:

Different objects respond to the same message in their own way:

17.1 Polymorphism

Different objects respond to the same message in their own way:

No if on type is needed: send a message and let the receiver decide. Replacing type tests (isKindOf:) with polymorphic messages is a core Smalltalk skill.

{ Rect width: 2 height: 3 . Circle radius: 1 . Square side: 2 }
    collect: [ :shape | shape area ].       "→ #(6 3.14159... 4)"

#(1 'one' #one $1 1.0) collect: [ :each | each printString ].

17.2 Method lookup

When an object receives a message:

Look for the selector in the method dictionary of the receiver's class.

If not found, look in the superclass, then its superclass, … up to ProtoObject.

If found, execute the method with self bound to the receiver.

If not found anywhere, send doesNotUnderstand: aMessage to the receiver — starting the lookup again. Object>>doesNotUnderstand: raises MessageNotUnderstood, which opens the debugger.

For super, lookup starts in the superclass of the method's defining class — determined at compile time.

The VM speeds this up with inline caches (per send site) and a global method cache — so dynamic dispatch is fast.

aSquare area
  Square        — no #area
  Rect     — #area found → execute with self = aSquare

17.3 doesNotUnderstand:

The Message object passed in contains the selector and arguments; you can override doesNotUnderstand: to implement proxies, dynamic accessors, or delegation:

Subclassing ProtoObject (which understands very few messages) makes almost every message fall through to doesNotUnderstand:. Use with care: tools may struggle to inspect proxies.

The same technique implements dynamic records: a doesNotUnderstand: that maps unary selectors to dictionary lookups and one-argument keyword selectors to at:put: (always fall back to super doesNotUnderstand: aMessage).

ProtoObject << #LoggingProxy
    slots: { #target };
    package: 'Demo'

LoggingProxy class >> on: anObject
    ^ self basicNew setTarget: anObject

LoggingProxy >> setTarget: anObject
    target := anObject

LoggingProxy >> doesNotUnderstand: aMessage
    Transcript crShow: 'Intercepted: ', aMessage selector printString.
    ^ aMessage sendTo: target
| p |
p := LoggingProxy on: (OrderedCollection new).
p add: 3; add: 4.
p size.          "→ 2, and the Transcript shows each intercepted message"

17.4 respondsTo: and type tests

Methods like isString are defined in Object (answering false) and overridden in the relevant class (answering true) — itself an application of polymorphism.

x respondsTo: #do:.              "duck-typing check — use sparingly"
x isString.  x isNumber.  x isCollection.  x isSymbol.  x isBlock.