5.4 Sintaxis de bloque

Los bloques proporcionan un mecanismo para aplazar la evaluación de expresiones. Un bloque es esencialmente una función anónima. Un bloque se evalúa enviándole el mensaje #value. El bloque responde con el valor de la última expresión de su cuerpo, a menos que haya un retorno explícito (con ^), en cuyo caso devuelve el valor de la expresión siguiente.

[ 1 + 2 ] value
⇒ 3

Los bloques pueden tomar parámetros, cada uno de los cuales se declara con dos puntos al principio. Una barra vertical separa la declaración de los parámetros del cuerpo del bloque. Para evaluar un bloque con un parámetro, debe enviarle el mensaje #value: con un argumento. A un bloque de dos parámetros se le debe enviar #value:value:, y así sucesivamente, hasta un máximo de 4 argumentos:

[ :x | 1 + x ] value: 2
⇒ 3
[ :x :y | x + y ] value: 1 value: 2
⇒ 3

Si tienes un bloque con más de cuatro parámetros, debes usar #valueWithArguments: y pasar los argumentos en una matriz. (Un bloque con un gran número de parámetros suele ser señal de un problema de diseño).

Los bloques también pueden declarar variables locales, que están rodeadas por barras verticales, al igual que las declaraciones de variables locales en un método. Las variables locales se declaran después de cualquier argumento:

[ :x :y | | z | z := x + y. z ] value: 1 value: 2
⇒ 3

Los bloques pueden hacer referencia a variables del entorno circundante. Se dice que los bloques «cierran» su entorno léxico, lo cual es una forma elegante de decir que recuerdan y hacen referencia a variables en su contexto léxico circundante, es decir, aquellas que aparecen en el texto que los rodea.

El siguiente bloque hace referencia a la variable x de su entorno circundante:

|x|
x := 1.
[ :y | x + y ] value: 2
⇒ 3

Los bloques son instancias de la clase BlockClosure. Esto significa que son objetos, por lo que se pueden asignar a variables y pasar como argumentos al igual que cualquier otro objeto.

Considera el siguiente ejemplo para calcular los divisores de un número entero:

| 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)"

Ejemplo 5.2: Calcular divisores

El problema con este ejemplo es la duplicación del código en el cálculo del divisor. Podemos evitar la duplicación con un bloque dedicado que realice el cálculo y lo asigne a una variable:

 CuisLogo Cómo reescribirías el Ejemplo 5.2 para evitar la duplicación de código?

Ejercicio 5.1: Calcular divisores con un bloque

El método SpaceWar>>teleport: contiene un buen ejemplo del uso de un bloque para evitar la duplicación de código al generar coordenadas aleatorias de abscisa y ordenada. Cada vez que se necesita una nueva coordenada, se envía el mensaje #value al bloque de código:

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

Ejemplo 5.3: Método teleport: