So far we have seen how to use individual expressions or sequences of expressions to send messages to objects and cause things to happen in our Smalltalk world. However, we are not going to be able to progress much further without the ability to perform tests and make decisions based on the results of these tests. We are also going to want to perform some repetitive tasks (well that's what computers are good at after all) so we'll want to see how Smalltalk handles looping repeatedly around blocks of code.
We are going to use a Playground window once more to demonstrate the examples in this chapter. You will also need to open a suitable workspace and then create a Playground and populate it as before:
playground := Playground new. teresa := playground add: Triangle new. simon := playground add: Square new. charlie := playground add: Circle new.
Smalltalk compares two objects using boolean expressions. A boolean expression is one that yields a truth value, i.e. true or false. We can use a number of binary messages to perform comparisons. First of all make simon bigger than teresa and charlie and then let's try some comparisons expressions.
simon growBy: 10.
Display the results of the following comparisons:
simon radius > teresa radius. simon radius < 100. charlie radius = teresa radius. charlie position ~= teresa position.
The final expression is comparing the two positions to see if they are unequal. It's interesting here to note that, in this comparison, we are testing the equality of two points (the previous three comparisons were between numbers). So the comparison messages are not just limited to number objects. But not all objects can be compared since it all depends on whether they respond to the appropriate comparison messages. Try:
charlie >= teresa.
You got a does not understand walkback because it does not really make sense to compare the magnitude of a circle with that of a triangle.
Tip: you might of course decide that you wanted to consider the magnitude of a shape to be its area and you wanted to be able to use comparison messages based on this fact. Later you'll see how to define new messages for particular classes of object. Hence, if you wish, you'll be able to implement the comparison messages for Playground shapes.
All objects do respond to #= and #~=, however:
charlie = teresa. simon = simon. simon ~= charlie.
Here we are testing to see whether the two shapes are (or aren't) the same object.
Tip: by default, the #= message does compare two objects to see if they are one and the same (identical). However, you'll also see that it is possible to change the meaning of a message such that, in the case of shapes, #= could compare the shape areas instead.
Some objects understand messages that answer boolean true or false values. You can use these to test to see if an object is of a particular kind or in a particular state. Try these examples:
'this is not an integer' isInteger. 5 between: 0 and: 10. $A isLowercase. charlie respondsTo: #>=.
This last expression asks to see if charlie can understand a #>= message. We've already seen above that it can't so the false answer comes as no surprise.
Tip: many testing messages take the form #isXXX. You'll find that this helps to make code easy to read and also gives an indication that the message will answer a boolean value.
Now let's put these boolean expressions to some good use. Based on the result of a particular expression we might want to execute or skip a piece of code. This is how it's done in Smalltalk (you don't need to Display it):
(simon radius > charlie radius) ifTrue: [ MessageBox notify: 'Simon is bigger than Charles'].
What's going on here? The boolean expression testing the two radii answers true (because we grew simon slightly earlier). Like everything else, a boolean value such as true or false is still an object and is therefore capable of handling messages sent to it. Here we send it #ifTrue: which takes as its parameter a block of code to execute in the case that the receiver is indeed true. The block of code is enclosed in square brackets []. If the receiver is false then the block does not get executed.
There are also additional messages that can be sent to boolean objects along similar lines.
"#ifFalse:" (charlie color=Color green) ifFalse: [ "If charlie is not green make him so" charlie color: Color green ]. "#ifTrue:ifFalse:" (Time now hours>6) "Turn charlie off at night" ifTrue: [charlie color: Color yellow] ifFalse: [charlie color: Color black].
This message offers the choice between executing one of two blocks of code. You must remember, though, that #ifTrue:ifFalse: is a single keyword message taking two parameters. Hence there must not be a dot separator after the first block since this would terminate the statement at this point (and you'll subsequently receive an error from the Dolphin compiler).
For convenience, there is also the reverse message:
"#ifFalse:ifTrue:" (Time now hours>6) "Turn charlie off at night" ifFalse: [charlie color: Color black] ifTrue: [charlie color: Color yellow].
As you might expect by now, Smalltalk uses messages to perform looping operations too.
10 timesRepeat: [ teresa moveRight: 10 ]. 5 timesRepeat: [ simon growBy: 5 ].
The #timesRepeat: message sent to an integer will cause its parameter block of code to be executed that number of times. You can have quite a bit of fun with loops and limited animation in the Playground:
[ charlie position x > 0 ] whileTrue: [ charlie moveLeft: 10 ]
This is interesting; here we have two blocks at work. The #whileTrue: message is sent to a block of code that is expected to answer a boolean result. If this is true then the parameter block is executed. This process is repeated until the receiver block answers false. You've probably guessed already that there is also a similar #whileFalse: message:
[ teresa rotation >= 90 ] whileFalse: [ teresa rotateBy: 10 ].
Tip: you are probably noticing that the Playground window "flashes" while we are performing these bits of animation. This is because the Playground was not designed for smooth animation which really needs to be implemented using a process known as "double buffering". You can certainly implement smooth animation using Dolphin, it's just that the Playground has been kept as simple as possible to aid understanding. Note, also, that the speed of each animation step has been deliberately slowed down so you can see it happening. Dolphin is capable of drawing "frames" much faster than you see here.
That are two shortcut versions of #whileTrue and #whileFalse that can just be sent to a block of code to cause it to execute repeatedly while it answers true or false respectively. These messages do not require any parameters:
[ teresa moveLeft: 5. teresa position x > simon position x ] whileTrue.
Here we see that the value answered from a code block when it is executed is that of the last expression in the block. The above example moves teresa left and then compares its new horizontal position with that of simon. The result of this comparison is returned as the final result from the block and the #whileTrue message is designed to repeat until this eventually yields false.
Sometimes you'll want to loop a number of times and know where you are in the sequence as you do so. For example, let's say that we want to print a list of the square numbers from 1 to 12.
1 to: 12 do: [:n | Transcript show: n squared displayString; cr ].
After evaluating this code in the System Transcript window to see the results. You can send text strings to this window using a #show: message sent to the global variable, Transcript. More importantly, take a look at the #to:do: message and the format of the block it takes as its second parameter. The receiver is an integer which represents the starting value and the first parameter is taking to be the ending value. The block is then executed for this range with each value in turn being passed as a parameter (n) to the block. Yes, that's right; blocks can have parameters too.
If a block is to expect one or more parameters then they must be declared immediately after the opening bracket. Each parameter is preceded by a colon and separated by a space. The parameter list is terminated by a vertical bar character.
The #to:do: message in the previous example loops through a range of integer values stepping one at a time. If you wish to increase the size of the step then the message #to:by:do: exists for this purpose:
10 to: 120 by: 10 do: [:eachRadius | simon radius: eachRadius ]. 40 to: 220 by: 20 do: [:eachAngle | simon rotation: eachAngle ]
In a later chapter you will see how you can iterate over more complicated collections of objects, not just integer ranges.
So far we have only used boolean expressions containing a single condition. Sometimes it is necessary to use a combination of conditions that make up a more complex expression. Try displaying the results of the following to see if you're working too hard:
Time now hours>22 and: [Time now hours<6].
If the current time is between 10 p.m. and 6 a.m., this expression will answer true. By now you should be able to see what's going on here. The #and: message is being received by a boolean value (the result of Time now hours>22) and being passed another condition in a block of code which needs to be evaluated if the receiver is true. The result of the #and: message is that of the combined condition.
An #or: message is also available:
charlie radius: 40. [simon radius > charlie radius or: [simon radius > teresa radius]] whileTrue: [ simon shrinkBy: 10 ].
This shrinks simon to be the same size as the smaller of the other two shapes. Notice that, in this situation, the #or: expression has being enclosed in another block so that it can be repeatedly evaluated by #whileTrue: until the required size is reached.
If you are used using other computer languages using these messages may look a little odd at first. You're probably used to using something like:
if (simon radius > charlie radius) { printf("Simon is bigger than Charles") }
in which case, the Smalltalk equivalent:
(simon radius > charlie radius) ifTrue: [ MessageBox notify: 'Simon is bigger than Charles'].
looks somewhat "backwards" and perhaps harder to read. I know that I originally thought this when learning Smalltalk for the first time. However, I guarantee that you'll soon get used to it.
The beauty comes when you realize that #ifTrue: is a message and not part of the Smalltalk language definition at all. The grammar of the traditional language includes the definition of how an "if" statement is formed but Smalltalk needs no such definition. Instead, the control flow is implemented solely with messages. This makes the "language" smaller, more regular, and easier to learn because it has many fewer elements to it. It is also more flexible since you could (if you wished) define new messages to perform similar operations.
Of course, all this also applies equally to the other control flow messages: #whileTrue:, #whileTrue, #whileFalse:, #whileFalse, #repeat, #timesRepeat:, #to:do: and #to:by:do:.
In this chapter you have learned about:
Please tidy up your workspace and Playground windows before proceeding with the next chapter.
Click here to move on to the next chapter or here to go back to the previous chapter.