3.6: Conditionals and Loops in a Nutshell
- Page ID
Smalltalk offers no special syntax for control constructs. Instead, these are typically expressed by sending messages to booleans, numbers and collections, with blocks as arguments.
Conditionals are expressed by sending one of the messages
ifTrue:ifFalse: to the result of a boolean expression. See Chapter 8 for more about booleans.
(17 * 13 > 220) ifTrue: [ 'bigger' ] ifFalse: [ 'smaller' ] → 'bigger'
Loops are typically expressed by sending messages to blocks, integers or collections. Since the exit condition for a loop may be repeatedly evaluated, it should be a block rather than a boolean value. Here is an example of a very procedural loop:
n := 1. [ n < 1000 ] whileTrue: [ n := n*2 ]. n → 1024
whileFalse: reverses the exit condition.
n := 1. [ n > 1000 ] whileFalse: [ n := n*2 ]. n → 1024
timesRepeat: offers a simple way to implement a fixed iteration:
n := 1. 10 timesRepeat: [ n := n*2 ]. n → 1024
We can also send the message
to:do: to a number which then acts as the initial value of a loop counter. The two arguments are the upper bound, and a block that takes the current value of the loop counter as its argument:
result := String new. 1 to: 10 do: [:n | result := result, n printString, ' ']. result → '12345678910'
High-Order Iterators. Collections comprise a large number of different classes, many of which support the same protocol. The most important messages for iterating over collections include
inject:into:. These messages define high-level iterators that allow one to write very compact code.
Interval is a collection that lets one iterate over a sequence of numbers from the starting point to the end.
1 to: 10 represents the interval from 1 to 10. Since it is a collection, we can send the message
do: to it. The argument is a block that is evaluated for each element of the collection.
result := String new. (1 to: 10) do: [:n | result := result, n printString, ' ']. result → '12345678910'
collect: builds a new collection of the same size, transforming each element.
(1 to: 10) collect: [ :each | each * each] → #(149162536496481100)
reject: build new collections, each containing a subset of the elements satisfying (or not) the boolean block condition.
detect: returns the first element satisfying the condition. Don’t forget that strings are also collections, so you can iterate over all the characters.
'hello there' select: [ :char | char isVowel ] → 'eoee' 'hello there' reject: [ :char | char isVowel ] → 'hll thr' 'hello there' detect: [ :char | char isVowel ] → $e
Finally, you should be aware that collections also support a functional-style fold operator in the
inject:into: method. This lets you generate a cumulative result using an expression that starts with a seed value and injects each element of the collection. Sums and products are typical examples.
(1 to: 10) inject: 0 into: [:sum: each |sum + each] → 55
This is equivalent to 0+1+2+3+4+5+6+7+8+9+10.
More about collections can be found in Chapter 9.