Functions in Smalltalk
Learn how Smalltalk handles functions through methods and blocks - first-class closures, recursion, and higher-order programming with Docker-ready examples
Most languages have standalone functions you call by name. Smalltalk does not. In a pure object-oriented, message-passing language, the closest thing to a “function” comes in two flavors: methods, which are behaviors that belong to objects, and blocks, which are anonymous, first-class closures you can store, pass around, and invoke on demand.
Because Smalltalk is reflective and dynamic, both methods and blocks are themselves objects. A method is something you ask an object to perform by sending it a message; a block is a BlockClosure instance that runs when you send it the value message. There is no global namespace of free functions — every piece of behavior is reached by sending a message to a receiver.
This page builds on the message-passing ideas from Hello World and shows how to define methods on classes, capture logic in blocks, manage variable scope, write recursion, and use higher-order programming where blocks are passed as arguments. All examples run unchanged with GNU Smalltalk in Docker.
Methods: Functions That Belong to Objects
A method is defined inside a class body and invoked by sending the matching message to an instance. Method names follow the same three message forms as everything else in Smalltalk: unary, binary, and keyword. The caret (^) returns a value; without it, a method returns the receiver (self).
Create a file named methods.st:
| |
The method add:to: is a single message with two keyword parts. You call it as calc add: 3 to: 4, which reads almost like a sentence. This is how Smalltalk encodes named parameters directly into the message selector.
Blocks: First-Class Closures
A block is written with square brackets. Arguments are declared after a colon and separated from the body by a vertical bar. You run a block by sending it value, value:, value:value:, and so on, depending on how many arguments it takes. Blocks are ordinary objects, so you can assign them to variables and pass them anywhere.
Create a file named blocks.st:
| |
The comma (,) is a binary message that concatenates strings, so 'Hello, ', name, '!' builds a new string. Note that the number of value: keywords must match the number of block arguments — a block expecting two arguments cannot be run with value:.
Scope and Closures
Blocks are true closures: they capture the variables in their surrounding scope and keep those variables alive even after the enclosing block has returned. This lets you build stateful behavior — like a counter — without any global variables.
Create a file named closures.st:
| |
The temporary variable count is declared with | count | inside the outer block, making it local to that scope. The inner block references it, so count survives as long as the inner block does. Each counter value call resumes the same captured state — a clean demonstration of lexical scoping.
Recursion
Smalltalk has no special looping keywords; iteration and recursion are both expressed through messages. Recursion is natural here — a method simply sends itself (or another message that leads back to itself) until a base case is reached. We add new behavior to the built-in Integer class using extend.
Create a file named recursion.st:
| |
Here self is the receiving integer. The base case uses ifTrue: (itself a keyword message sent to a boolean) with an early ^ return. Because every integer is an object, 5 fact is just a unary message — exactly the same mechanism as 5 negated or 'hello' size.
Higher-Order Functions
Because blocks are objects, you can pass them as arguments and return them from methods. The collection protocol is built entirely on this idea: collect: maps, select: filters, and inject:into: folds. You can also write your own methods that accept blocks.
Create a file named higher_order.st:
| |
The method applyTwice:to: treats its block parameter like any other object, sending it value: twice. This is the heart of higher-order programming in Smalltalk: control structures, iteration, and your own abstractions are all just objects (often blocks) receiving messages.
Running with Docker
Run each example with the official GNU Smalltalk image. The gst command executes a .st script file directly.
| |
Expected Output
The output below shows each file’s result in order: methods.st, blocks.st, closures.st, recursion.st, then higher_order.st.
36
7
42
Hello, Smalltalk!
30
1
2
3
120
720
55
(1 4 9 16 25 36 )
(2 4 6 )
21
25
Key Concepts
- No free functions — Smalltalk has no standalone functions. Behavior lives in methods (defined on classes) and blocks (anonymous closures). Everything is invoked by sending a message.
- Keyword selectors are named parameters — A method like
add:to:is one message with two argument slots, read naturally ascalc add: 3 to: 4. - Blocks are first-class objects — A
[ :x | ... ]block is a real object you can store, pass, return, and run later withvalue,value:,value:value:, and so on. ^returns, otherwise you getself— A method without an explicit caret return answers the receiver, not the last expression.- Closures capture lexical scope — Blocks keep referenced outer variables alive, enabling stateful constructs like counters without globals.
- Recursion over loops — With no loop keywords, recursion and message-based iteration (
timesRepeat:,do:) replace traditionalfor/while. - Higher-order programming is idiomatic —
collect:,select:, andinject:into:build the entire collection protocol on blocks, and your own methods can accept blocks just as easily.
Running Today
All examples can be run using Docker:
docker pull sl4m/gnu-smalltalk:latest
Comments
Loading comments...
Leave a Comment