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 printString30.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.