Control Flow
Beamtalk has no if, while, or for keywords. Control flow is expressed
through messages sent to booleans, numbers, and blocks. This is pure
Smalltalk heritage — and it's more powerful than it first appears.
Conditionals: ifTrue: ifFalse:
Send ifTrue: to a boolean with a block. The block runs if the boolean is true.
true ifTrue: ["yes"] // => yes
false ifTrue: ["yes"] // => false
ifFalse: — runs the block when false:
false ifFalse: ["no"] // => no
true ifFalse: ["no"] // => true
ifTrue:ifFalse: — like if/else:
true ifTrue: ["yes"] ifFalse: ["no"] // => yes
false ifTrue: ["yes"] ifFalse: ["no"] // => no
The result of ifTrue:ifFalse: is the value of whichever block ran:
score := 85 // => _
grade := (score >= 90) ifTrue: ["A"] ifFalse: [(score >= 80) ifTrue: ["B"] ifFalse: ["C"]] // => _
grade // => B
Nil-aware conditionals
nil ifNil: ["nothing here"] // => nothing here
nil ifNil: ["nothing"] ifNotNil: [:v | "got {v}"] // => nothing
42 ifNil: ["nothing"] ifNotNil: [:v | "got {v}"] // => got 42
42 ifNotNil: [:v | v * 2] // => 84
nil ifNotNil: [:v | v * 2] // => nil
whileTrue: / whileFalse:
Send whileTrue: to a condition block (a block that returns a boolean).
The body block runs repeatedly while the condition is true.
i := 0 // => _
[i < 5] whileTrue: [i := i + 1] // => _
i // => 5
whileFalse: — runs the body until the condition becomes true:
j := 0 // => _
[j >= 3] whileFalse: [j := j + 1] // => _
j // => 3
Building a result inside whileTrue::
result := 0 // => _
k := 1 // => _
[k <= 10] whileTrue: [result := result + k. k := k + 1] // => _
result // => 55
timesRepeat:
Run a block a fixed number of times:
count := 0 // => _
5 timesRepeat: [count := count + 1] // => _
count // => 5
to:do: — numeric iteration
Iterate over a range of integers (inclusive at both ends):
sum := 0 // => _
1 to: 5 do: [:n | sum := sum + n] // => _
sum // => 15
to:by:do: — with a custom step:
evens := 0 // => _
2 to: 10 by: 2 do: [:n | evens := evens + n] // => _
evens // => 30
Counting down (negative step):
countdown := 0 // => _
5 to: 1 by: -1 do: [:n | countdown := countdown + n] // => _
countdown // => 15
to:collect: — build an array from a range:
squares := (1 to: 5) collect: [:n | n * n] // => _
squares // => [1,4,9,16,25]
and: / or: — short-circuit boolean logic
and: and or: are keyword messages that take blocks. The block is only
evaluated if needed (short-circuit evaluation).
x := 5 // => _
x > 0 and: [x < 10] // => true
x > 0 or: [x < 0] // => true
Without short-circuit (the block is not evaluated when not needed):
false and: [1/0] // => false
true or: [1/0] // => true
Case-like dispatch: match:
Use match: for switch/case-style dispatch on a value.
Arms are separated by ;, with -> between pattern and result.
A _ wildcard arm acts as the default/otherwise case.
day := #monday // => _
(day match: [#monday -> "Start of work week"; #friday -> "End of work week"; #saturday -> "Weekend!"; #sunday -> "Weekend!"; _ -> "Midweek"]) // => Start of work week
(#wednesday match: [#monday -> "Monday"; #friday -> "Friday"; _ -> "Other day"]) // => Other day
Early exit with ^
Inside a method, ^ returns from the method immediately.
At the top level of a script, ^ returns the value from the current expression.
A common pattern: validate inputs and return early:
// processOrder: order =>
// order isNil ifTrue: [^"error: nil order"]
// order isEmpty ifTrue: [^"error: empty order"]
// // ... normal processing
// "ok"
Inside a block (inside a method), ^ exits the method, not just the block:
// firstPositive: items =>
// items do: [:each |
// each > 0 ifTrue: [^each]]
// nil
Summary
Conditionals:
bool ifTrue: [...]
bool ifFalse: [...]
bool ifTrue: [...] ifFalse: [...]
val ifNil: [...] ifNotNil: [:v | ...]
Loops:
[condition] whileTrue: [body]
[condition] whileFalse: [body]
n timesRepeat: [body]
start to: end do: [:i | body]
start to: end by: step do: [:i | body]
Logic:
bool and: [...] (short-circuit)
bool or: [...] (short-circuit)
Dispatch:
val match: [pattern -> result; pattern -> result; _ -> default]
Exercises
1. Sum 1 to 100. Use whileTrue: to compute the sum of all integers from 1
to 100. Verify the result is 5050.
Hint
sum := 0
i := 1
[i <= 100] whileTrue: [sum := sum + i. i := i + 1]
sum // => 5050
Or more concisely with to:do:: sum := 0. 1 to: 100 do: [:n | sum := sum + n].
2. Number classifier. Write a match: expression that classifies an integer
as "negative", "zero", or "positive" using guard expressions.
Hint
classify := [:n |
n match: [
x when: [x < 0] -> "negative";
0 -> "zero";
_ -> "positive"
]
]
classify value: -5 // => "negative"
classify value: 0 // => "zero"
classify value: 42 // => "positive"
3. Odd sum. Use to:by:do: to sum all odd numbers from 1 to 19. Verify the
result is 100.
Hint
sum := 0
1 to: 19 by: 2 do: [:n | sum := sum + n]
sum // => 100
Odd numbers from 1 to 19: 1, 3, 5, 7, 9, 11, 13, 15, 17, 19 = 100.
Next: Chapter 9 — Collections