Control Flow in Smalltalk
Learn control flow in Smalltalk - conditionals, loops, and iteration are all ordinary messages sent to objects and evaluated with blocks
Most languages bake control flow into their grammar: if, while, and for are keywords the compiler treats specially. Smalltalk does something radical and beautiful instead - there are no control-flow keywords at all. Conditionals and loops are ordinary messages sent to ordinary objects, and the “branches” are blocks (closures) passed as arguments.
This is the natural consequence of Smalltalk’s central idea: everything is an object, and everything happens through message passing. When you write age >= 18 ifTrue: [ ... ], you are sending the keyword message ifTrue: to a Boolean object (true or false), handing it a block to evaluate. The Boolean decides whether to run the block. The same pattern - send a message, pass a block - drives every loop and iterator in the language.
In this tutorial you’ll learn how Smalltalk expresses conditionals, multi-way branching, condition-controlled loops, counted loops, and collection iteration. Because these are all just messages, conditionals can return values (Smalltalk’s answer to the ternary operator), and you can define your own control structures the same way the standard library defines whileTrue:.
Conditionals with Blocks
The fundamental conditional is the keyword message ifTrue:ifFalse:, sent to a Boolean. Each branch is a block - code wrapped in square brackets that is only evaluated if its branch is selected.
Create a file named conditionals.st:
| |
Notice that and: takes a block as its argument, not a plain expression. This gives Smalltalk short-circuit evaluation: the right-hand block is only evaluated when the left side is true. The messages displayNl print an object followed by a newline (unlike printNl, displayNl shows strings without surrounding quotes).
Conditionals Are Expressions
Because ifTrue:ifFalse: is a message, it returns a value - specifically the value of whichever block it evaluated. This is how Smalltalk does what other languages call the ternary operator (cond ? a : b), and it scales naturally to multi-way branching. Smalltalk has no switch/case keyword; you chain conditionals instead.
Create a file named conditional_values.st:
| |
Here Transcript show: expects a string, so numbers are converted explicitly with printString. The nested ifTrue:ifFalse: reads top to bottom as a cascade of range checks - the Smalltalk idiom for the else if ladder you’d write elsewhere.
Condition-Controlled Loops
A while loop in Smalltalk is the message whileTrue: sent to a block. The receiver block computes the condition, and the argument block is the loop body. Smalltalk repeatedly evaluates the receiver; as long as it answers true, the body runs again.
Create a file named while_loops.st:
| |
The square brackets around the condition are essential: [ count <= 5 ] is a block that re-evaluates each pass. If you wrote count <= 5 without brackets, it would be computed only once - a fixed true or false - and the loop would never re-check. The whileFalse: variant subtracts 3 from 10 until the value drops to or below zero, landing on -2.
Counted and Stepping Loops
For counting loops, Smalltalk sends iteration messages to numbers. timesRepeat: repeats a block a fixed number of times, while to:do: and to:by:do: walk a numeric range, binding the current value to a block parameter (the :i part).
Create a file named counted_loops.st:
| |
The block parameter :i is declared between the brackets and a vertical bar, then used in the body. This is the same block syntax you’ve seen everywhere - to:do: simply evaluates your block once per number in the range, passing the current value each time.
Iterating Collections and Exiting Early
Collections are iterated with messages too, and this is where Smalltalk’s approach shines. Rather than writing index-based loops with break and continue (Smalltalk has neither keyword), you choose the message that expresses your intent: do: to visit every element, select:/reject: to filter, and detect:ifNone: to find the first match and stop - the natural replacement for a “break when found” loop.
Create a file named collection_control.st:
| |
There is no continue either - if you want to skip elements, you wrap the body in a conditional or use select: to filter first. This “pick the right message” style means your code states what you want done to the collection rather than spelling out the mechanics of an index counter.
Running with Docker
The examples use GNU Smalltalk via Docker, so you don’t need to install anything locally.
| |
Expected Output
Running conditionals.st:
You are an adult
Not a young child
Pleasant weather
Running conditional_values.st:
Result: pass
Grade: C
7 is odd
Running while_loops.st:
Count: 1
Count: 2
Count: 3
Count: 4
Count: 5
n ended at: -2
5! = 120
Running counted_loops.st:
Knock
Knock
Knock
Square of 1 is 1
Square of 2 is 4
Square of 3 is 9
Square of 4 is 16
Square of 5 is 25
Even numbers: 0 2 4 6 8 10
Sum 10 down to 1: 55
Running collection_control.st:
All numbers: 3 7 2 8 5 10
First even number: 2
Evens: (2 8 10 )
Odds: (3 7 5 )
Ada, Alan, Grace
Key Concepts
- Control flow is message passing. There are no
if,while, orforkeywords.ifTrue:ifFalse:,whileTrue:,to:do:, anddetect:ifNone:are ordinary messages sent to objects (Booleans, blocks, numbers, collections). - Blocks are the branches. Code in
[ ... ]is a closure that is only evaluated if its branch is selected. A condition that must re-evaluate (like a loop test) must be wrapped in a block, or it computes only once. - Conditionals return values.
ifTrue:ifFalse:answers the value of the block it ran, giving Smalltalk a ternary operator for free and letting you assign the result directly. - No switch statement. Multi-way branching is expressed by nesting
ifTrue:ifFalse:into anelse ifladder, or - more idiomatically in large programs - by polymorphism (different objects responding to the same message). and:/or:take blocks for short-circuit evaluation, so the second condition is only checked when necessary.- No
breakorcontinue. To stop early, choose a message that stops on its own, likedetect:ifNone:. To skip elements, filter withselect:/reject:or guard the body with a conditional. - Pick the message that states intent.
do:to visit all,select:/reject:to filter,detect:to find one,timesRepeat:to repeat - the message name documents what the loop does. - You can build your own control structures. Because loops and conditionals are just messages plus blocks, defining a new one is the same as defining any other method - a level of extensibility most languages can’t match.
Running Today
All examples can be run using Docker:
docker pull sl4m/gnu-smalltalk:latest
Comments
Loading comments...
Leave a Comment