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