Reflection & Meta-Programming

Smalltalk is fully reflective: classes, methods, the compiler, and the execution stack are ordinary objects you can query (*introspection*) and change (*intercession*) at runtime.

Smalltalk is fully reflective: classes, methods, the compiler, and the execution stack are ordinary objects you can query (*introspection*) and change (*intercession*) at runtime.

30.1 Introspection

| acct |
acct := BankAccount owner: 'Ada' balance: 100.
acct class.                              "→ BankAccount"
acct respondsTo: #deposit:.              "→ true"
acct class allInstVarNames.              "→ #(#owner #balance)"
acct instVarNamed: 'balance'.            "→ 100 — bypasses encapsulation (for tools!)"
acct instVarNamed: 'balance' put: 1000000.
acct instVarAt: 1.                       "→ 'Ada'"
BankAccount selectors.   BankAccount allSelectors size.   BankAccount allSubclasses.
BankAccount allInstances.                "every live instance in the image"
(BankAccount >> #deposit:) sourceCode.   (BankAccount >> #deposit:) literals.
(BankAccount >> #deposit:) messages.     "selectors this method sends"
SystemNavigation default allSendersOf: #deposit:.
#deposit: senders.  #printOn: implementors.     "Pharo shortcuts on Symbol"

30.2 Dynamic message sending

Uses: generic UI bindings, serializers, command dispatch, test frameworks (SUnit finds and performs test* selectors).

3 perform: #factorial.                               "→ 6"
3 perform: #+ with: 4.                               "→ 7"
3 perform: #between:and: with: 1 with: 5.            "→ true"
acct perform: #deposit: withArguments: #(50).
(Message selector: #raisedTo: arguments: #(10)) sendTo: 2.     "→ 1024"
acct perform: (#balance , ':') asSymbol with: 0.     "selector built at runtime (setter, if defined)"

30.3 Compiling methods at runtime

Creating and removing classes:

BankAccount compile: 'interest
    ^ balance * 5 / 100' classified: 'calculations'.
(BankAccount owner: 'Ada' balance: 200) interest.    "→ 10"
BankAccount removeSelector: #interest.

"Evaluate source code"
Smalltalk compiler evaluate: '3 + 4'.                "→ 7"
(Smalltalk compiler source: '[:x | x * 2]') evaluate value: 21.   "→ 42"
(Object << #Generated slots: { #a }; package: 'Gen-Demo') install.
(Smalltalk globals at: #Generated) new.
(Smalltalk globals at: #Generated) removeFromSystem.

30.4 thisContext: the stack as objects

Walking ctx := ctx sender until nil gives the whole call chain. thisContext is how the debugger, exceptions (signalerContext), continuations (Seaside), and generators are implemented — entirely in Smalltalk.

BankAccount >> whoCalledMe
    ^ thisContext sender method printString

30.5 Pragmas

Pragmas annotate methods with metadata that tools and frameworks can query:

The <worldMenu> pragma adds entries to Pharo's World menu; <script>, <example>, <baseline>, <primitive: n>, and <gtInspectorPresentationOrder:>-style pragmas drive other tools.

MyTool class >> menuCommandOn: aBuilder
    <worldMenu>
    (aBuilder item: #'My Tool') action: [ self open ]

(Pragma allNamed: #example in: BankAccount class) collect: [ :p | p method selector ].

30.6 The AST and MetaLinks (Reflectivity)

Pharo exposes method ASTs and lets you attach behavior to any node without editing source:

Breakpoints, watchpoints, and code coverage in Pharo are built on MetaLinks.

(BankAccount >> #deposit:) ast.                       "→ an RBMethodNode"
(BankAccount >> #deposit:) ast allChildren size.

| link |
link := MetaLink new
    metaObject: [ Transcript crShow: 'deposit called' ];
    selector: #value;
    control: #before.
(BankAccount >> #deposit:) ast link: link.            "instrument the method"
(BankAccount owner: 'Ada') deposit: 10.              "Transcript: deposit called"
link uninstall.

30.7 become:

become: swaps identities of two objects (two-way); becomeForward: redirects references one way. Used for proxies, lazy loading (object databases), and schema migration. Use with great care.

| a b |
a := 'original' copy.
b := 'replacement' copy.
a becomeForward: b.        "every reference to a now points to b"

30.8 Reflection pitfalls

instVarNamed: and computed perform: selectors defeat static analysis, senders searches, and refactorings: use reflection in frameworks and tools, polymorphism in domain code.