5.5 Control de flujo con bloques y mensajes

La decisión de enviar este mensaje en lugar de aquél se denomina control de flujo, es decir, controlar el flujo de un cálculo. Smalltalk no ofrece construcciones especiales para el control de flujo. La lógica de decisión se expresa enviando mensajes a booleanos, números y colecciones con bloques como argumentos.

Condicional

Las condiciones se expresan enviando uno de los mensajes #ifTrue:, #ifFalse: o #ifTrue:ifFalse: al resultado de una expresión booleana:

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

La clase Boolean ofrece una visión fascinante de cuánto del lenguaje Smalltalk se ha incorporado a la biblioteca de clases. Boolean es la superclase abstracta de las clases Singleton True y False20.

La mayor parte del comportamiento de las instancias Boolean puede entenderse considerando el método ifTrue:ifFalse:, que toma dos bloques como argumentos:

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

El método es abstracto en Boolean. Se implementa en sus subclases concretas True y False:

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

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

Ejemplo 5.4: Implementaciones de ifTrue:ifFalse:

De hecho, esta es la esencia de la POO: cuando se envía un mensaje a un objeto, el propio objeto determina qué método se utilizará para responder. En este caso, una instancia de True simplemente evalúa la alternativa verdadera, mientras que una instancia de False evalúa la alternativa falsa. Todos los métodos abstractos Boolean se implementan de esta manera para True y False. Veamos otro ejemplo:

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

Ejemplo 5.5: Implementación de la negación

Los booleanos ofrecen varios métodos útiles, como ifTrue:, ifFalse:, ifFalse:ifTrue:. También puedes elegir entre conjunciones y disyunciones rápidas y perezosas:

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

En el primer ejemplo, se evalúan ambas subexpresiones Boolean, ya que & toma un argumento Boolean. En el segundo y tercer ejemplo, solo se evalúa la primera, ya que and: espera un Block como argumento. El Block solo se evalúa si el primer argumento es verdadero.

 CuisLogo Intenta imaginar cómo están implementados and: y or:.

Ejercicio 5.2: Implementar and: y or:

En el Ejemplo 5.1 al principio de este capítulo, hay 4 mensajes de flujo de control #ifTrue:. Cada argumento es un bloque de código y, cuando se evalúa, devuelve explícitamente una expresión, interrumpiendo así la ejecución del método.

En el fragmento de código del Ejemplo 5.6 a continuación, comprobamos si una nave se ha perdido en el espacio profundo. Depende de dos condiciones:

  1. la nave está fuera del área de juego, probado con el mensaje #isInOuterSpace,
  2. la nave toma la dirección del espacio profundo, probada con el mensaje #isGoingOuterSpace.

Por supuesto, la condición n.º 2 solo se comprueba cuando la condición n.º 1 es verdadera.

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

Ejemplo 5.6: Nave perdida en el espacio

Bucle

Los bucles suelen expresarse enviando mensajes a bloques, enteros o colecciones. Dado que la condición de salida de un bucle puede evaluarse repetidamente, debe ser un bloque en lugar de un valor booleano. A continuación se muestra un ejemplo de un bucle muy procedimental:

n := 1.
[ n < 1000 ] whileTrue: [ n := n * 2 ].
n ⇒ 1024

#whileFalse: invierte la condición de salida:

n := 1.
[ n > 1000 ] whileFalse: [ n := n * 2 ].
n ⇒ 1024

Puedes consultar todas las alternativas en la categoría de métodos de control de la clase BlockClosure.

#timesRepeat: ofrece una manera sencilla de implementar una iteración:

n := 1.
10 timesRepeat: [ n := n * 2 ].
n ⇒ 1024

También podemos enviar el mensaje #to:do: a un número que luego actúa como el valor inicial de un contador de bucle. Los dos argumentos son el límite superior y un bloque que toma el valor actual del contador de bucle como su argumento:

result := String new.
1 to: 10 do: [:n | result := result, n printString, ' '].
result ⇒ '1 2 3 4 5 6 7 8 9 10 '

Puedes consultar todas las alternativas en la categoría del método intervals de la clase Number.

 note Si la condición de salida de un método como whileTrue: nunca se cumple, es posible que hayas implementado un bucle infinito. Solo tienes que pulsar Cmd-punto para acceder al depurador.


Notas al pie

(20)

Una clase singleton está destinada a tener sólo una instancia. Cada una de las clases True y False tiene sólo una instancia, los valores true y false.