Previous Table of Contents Next


Thus, by using polymorphic message sends, we can construct an expression that will conditionally perform one of two possible actions. However, how can we easily express these actions? Based on what we have described to this point, the only possible way would be to define a separate class and value method for each possible action. For example, we might define the following:

Class Action1 Instance Method
value
“Subtract B from A.”
^A-B

Class Action2 Instance Method/TT>
value
“Subtract A from B.”
^B-A

Using these definitions, we could write the example expressions as follows:

  A>B ifTrue: Action1 new ifFalse: Action2 new.

The argument expressions create new objects that respond appropriately to the value message. These objects are created and passed as the arguments of ifTrue:ifFalse:. If the expression A>B evaluates to true, then the ifTrue:ifFalse: method in class True is invoked and the message value is sent to the instance of Action1. Action1 responds by returning the result of A-B. Otherwise, the ifTrue:ifFalse: method in class False is invoked and the message value is sent to the instance of Action2. Action2 responds by returning the result of B-A.

It would be very cumbersome to have to create a new class every time we needed to express a conditional within a program. Large programs would require thousands of these classes, which would be difficult to manage and difficult to read. Note that we capitalized the variables A and B, indicating that they are global variables. We did this because we needed to directly access them from code fragments in at least three different classes.

Smalltalk avoids these problems by providing a syntactic construct called a block constructor that creates objects that uniquely respond to the message value. A block constructor is a sequence of expressions enclosed in square brackets. For example, [a+b] is a simple block constructor. The object that is created by a block constructor is called a block. The term block closure is sometimes used to describe these objects. When a block is sent the message value, it evaluates the expressions enclosed by the brackets of the block constructor. The value of the last of these expressions is returned as the result of the value message. Using blocks, our example can be written without defining any auxiliary classes:

  A>B ifTrue: [A-B] ifFalse: [B-A].

The code within a block has access to the same variable scopes to which it would have access if the block brackets were not present. Thus, we do not need to use global variables in our example. The block can access the same method argument, temporaries, and instance variables as any neighboring code that is outside the block. So the final form of our example conditional expression is

  a>b ifTrue: [a-b] ifFalse: [b-a].

Strictly by using polymorphic message sends and block constructors, Smalltalk can provide the equivalent of if-then-else statements. Smalltalk also includes the following short-hand variants:

  ifTrue:            No else clause

  ifFalse            No then clause

  ifFalse:ifTrue     Reversed order

4.4.2. Iterative Execution

Blocks are also used by Smalltalk to specify iteration. While loops are coded in Smalltalk using the message selectors whileTrue: and whileFalse:. Consider this line, for example:

  [temp < 10] whileTrue: [temp := temp + 1]

These messages are sent to a block that evaluates to a boolean object. The receiver block is repetitively evaluated, and each time it evaluates to the boolean object identified by the message selector (true for whileTrue:, false for whileFalse:), the argument block is evaluated. The first time the receiver block does not evaluate to the matching value, iteration terminates and execution continues with the next statement.

Blocks can accept one or more arguments. The formal parameter names for a block’s arguments are listed immediately following the opening bracket of the block. Each parameter name is preceded by a colon, and the final parameter name is followed by a vertical bar. Here are examples of blocks that take one, two, and three arguments:

  block1 := [:argument | someArray at: argument put: 0]

  block2 := [:arg1 :arg2 | someArray at: arg1 put: arg2]

  block3 := [:target :index :value | target at: index put: value]

Blocks that accept arguments are evaluated using variants of the value: message. One value: keyword is used to correspond to each formal parameter of the block. The value of each keyword argument is associated with the corresponding parameter, according to its position. The preceding blocks might be evaluated using the following expressions:

  block1 value: 5.  “the value of the argument is 5”

  block2 value: 9 value: ‘pickle’. “store ‘pickle’ in element 9”

  block3 value: (Array new: 5) value: 1 value: Customer new.

By using blocks with arguments, more complex control structures can be constructed. A classic do loop is created using a block with one argument:

  1 to: 10 by: 2 do: [:i| someArray at: i put: 0]

A variant form of this message (whose selector is to:do:) implicitly uses an increment (the by: argument) of 1. Although most Smalltalk compilers optimize the implementation of to:do:by: and to:do:, it is possible to implement them strictly using whileTrue:. Here is an example of such an implementation:

Class Number Instance Method
to: endValue by: increment do: aBlock
“iterate from the value of thereceiver to the endValue.
On each iteration, evaluate aBlock with the induction variable
as the argument. For this example the increment is assumed to be
positive.”
|i|
i := self.
[i <= endValue] whileTrue: [aBlock value: i. i := i + increment]


Previous Table of Contents Next