Blocks provide a mechanism to defer the evaluation of expressions. A
block is essentially an anonymous function. A block is evaluated by
sending it the message #value
. The block answers the value of the
last expression in its body, unless there is an explicit return (with
^), in which case it returns the value of the subsequent
expression).
[ 1 + 2 ] value ⇒ 3
Blocks may take parameters, each of which is declared with a leading
colon. A vertical bar separates the parameter declaration(s) from the
body of the block. To evaluate a block with one parameter, you must
send it the message #value:
with one argument. A two-parameter
block must be sent #value:value:
, and so on, up to 4 arguments:
[ :x | 1 + x ] value: 2 ⇒ 3 [ :x :y | x + y ] value: 1 value: 2 ⇒ 3
If you have a block with more than four parameters, you must use
#valueWithArguments:
and pass the arguments in an array. (A block
with a large number of parameters is often a sign of a design
problem.)
Blocks may also declare local variables, which are surrounded by vertical bars, just like local variable declarations in a method. Locals are declared after any arguments:
[ :x :y | | z | z := x + y. z ] value: 1 value: 2 ⇒ 3
Blocks can refer to variables of the surrounding environment. Blocks are said to “close over” their lexical environment, which is a fancy way to say that they remember and refer to variables in their surrounding lexical context – those apparent in their enclosing text.
The following block refers to the variable x of its enclosing environment:
|x| x := 1. [ :y | x + y ] value: 2 ⇒ 3
Blocks are instances of the class BlockClosure
. This means
that they are objects, so they can be assigned to variables and passed
as arguments just like any other object.
Consider the example below to compute the divisors of an integer:
| n m | n := 60. m := 45. (1 to: n) select: [:d | n \\ d = 0 ]. "⇒ #(1 2 3 4 5 6 10 12 15 20 30 60)" (1 to: m) select: [:d | m \\ d = 0] "⇒ #(1 3 5 9 15 45)"
The problem with this example is the code duplication in the divisor computation. We can avoid duplication with a dedicated block doing the computation and assigning it to a variable:
How will you rewrite Example 5.2 to avoid code duplication?
The SpaceWar>>teleport:
method contains a nice example
using a block to avoid code duplication to generate random abscissa
and ordinate coordinates. Each time a new coordinate is needed, the
message #value
is sent to the block of code:
SpaceWar>>teleport: aShip "Teleport a ship at a random location" | area randomCoordinate | aShip resupply. area := self morphLocalBounds insetBy: 20. randomCoordinate := [(area left to: area right) atRandom]. aShip velocity: 0 @ 0; morphPosition: randomCoordinate value @ randomCoordinate value