5.5 Control flow with block and message

Deciding to send this message instead of that one is called control flow – controlling the flow of a computation. Smalltalk offers no special constructs for control flow. Decision logic is expressed by sending messages to booleans, numbers and collections with blocks as arguments.

Test

Conditionals are expressed by sending one of the messages #ifTrue:, #ifFalse: or #ifTrue:ifFalse: to the result of a boolean expression:

(17 * 13 > 220)
  ifTrue: [ 'bigger' ]
  ifFalse: [ 'smaller' ]
⇒ 'bigger'

The class Boolean offers a fascinating insight into how much of the Smalltalk language has been pushed into the class library. Boolean is the abstract superclass of the Singleton classes True and False19.

Most of the behaviour of Boolean instances can be understood by considering the method ifTrue:ifFalse:, which takes two blocks as arguments:

(4 factorial > 20) ifTrue: [ 'bigger' ] ifFalse: [ 'smaller' ]
⇒ 'bigger'

The method is abstract in Boolean. It is implemented in its concrete subclasses True and False:

True>>ifTrue: trueAlternativeBlock ifFalse: falseAlternativeBlock
  ^ trueAlternativeBlock value

False>>ifTrue: trueAlternativeBlock ifFalse: falseAlternativeBlock
  ^ falseAlternativeBlock value

Example 5.4: Implementations of ifTrue:ifFalse:

In fact, this is the essence of OOP: when a message is sent to an object, the object itself determines which method will be used to respond. In this case an instance of True simply evaluates the true alternative, while an instance of False evaluates the false alternative. All the abstract Boolean methods are implemented in this way for True and False. Look at another example:

True>>not
   "Negation----answer false since the receiver is true."
   ^ false

Example 5.5: Implementing negation

Booleans offer several useful convenience methods, such as ifTrue:, ifFalse:, ifFalse:ifTrue:. You also have the choice between eager and lazy conjunctions and disjunctions:

(1 > 2) & (3 < 4)
⇒ false  "must evaluate both sides"
(1 > 2) and: [ 3 < 4 ]
⇒ false  "only evaluate receiver"
(1 > 2) and: [ (1 / 0) > 0 ]
⇒ false  "argument block is never evaluated, so no exception"

In the first example, both Boolean subexpressions are evaluated, since & takes a Boolean argument. In the second and third examples, only the first is evaluated, since and: expects a Block as its argument. The Block is evaluated only if the first argument is true.

 CuisLogo Try to imagine how and: and or: are implemented.

Exercise 5.2: Implementing and: and or:

In the Example 5.1 at the beginning of this chapter, there are 4 control flow #ifTrue: messages. Each argument is a block of code and when evaluated, it explicitly returns an expression, therefore interrupting the method execution.

In the code fragment of Example 5.6 below, we test if a ship is lost in deep space. It depends on two conditions:

  1. the ship is out of the game play area, tested with the #isInOuterSpace message,
  2. the ship takes the direction of deep space, tested with the #isGoingOuterSpace message.

Of course, the condition #2 is only tested when condition #1 is true.

"Are we out of screen?
If so we move the mobile to the other corner
and slow it down by a factor of 2"
(self isInOuterSpace and: [self isGoingOuterSpace])
  ifTrue: [
     velocity := velocity / 2.
     self morphPosition: self morphPosition negated]

Example 5.6: Ship lost in space

Loop

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

You can check all the alternatives in the controlling method category of the class BlockClosure.

#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 ⇒ '1 2 3 4 5 6 7 8 9 10 '

You can check all the alternatives in the intervals method category of the class Number.

 note If the exit condition of method like whileTrue: is never satisfied, you may have implemented an infinite loop. Just type Cmd-period to get the Debugger.


Footnotes

(19)

A singleton class is designed to have only one instance. Each of True and False classes has one instance, the values true and false.