Blocks y Procs

Los bloques de código (llamados closures en otros lenguajes) son definitivamente una de las características más divertidas de Ruby. Son bloques de código delimitados por corchetes ({}) o por las palabras do y end que pueden ser asociados con invocaciones de métodos “casi” como si fueran parámetros.

Un bloque de código Ruby es una manera de agrupar expresiones y pueden aparecer sólo junto al código de una llamada a un método. El bloque es escrito comenzando en la misma línea que el último parámetro de la llamada al método (o el cierre de paréntesis de la lista do parámetros). El código del bloque no es ejecutado en el momento en el que el intérprete lo encuentra: Ruby recuerda el contexto en el que el bloque aparece (las variables locales, el objeto actual, etc.) y entonces entra al método.

Es un estándar en Ruby usar corchetes ({}) para delimitar bloques de una sola línea y do end para bloques de más de una línea. Toma en cuenta que la sintaxis que usa corchetes tiene más alta precedencia que do end.

Matz dice que cualquier método puede ser llamado con un bloque como argumento implícito. Dentro del método, puedes llamar al bloque usando yield con un valor.

Una vez que has creado un bloque, puedes asociarlo con una llamada a un método. Usualmente los bloques de código que son pasados a métodos son objetos anónimos creados instantáneamente. Por ejemplo, en el código siguente, el bloque que contiene puts "hola" es asociado con la llamada al método saluda:

1 saluda {puts 'Hola'}

Si el método tiene prámetros, deben aparecer antes del bloque

1 saluda("Juan") {puts 'hola'}

Un método puede invocar un bloque asociado una o mås veces usando yield

El programa p022bloques.rb ilustra lo anterior

 1 =begin
 2   Los bloques de codigo en Ruby son pedazos de codigo
 3   entre corchetes o do end que pueden ser asociados
 4   a llamadas a metodos.
 5 =end
 6 def llama_un_bloque
 7   puts 'Inicio del metodo'
 8   # puedes llamar al bloque usando yield
 9   yield
10   yield
11   puts 'Fin del metodo'
12 end
13 # Los bloques de codigo pueden aparecer solo en el codigo
14 # adyacente a la llamada al metodo
15 llama_un_bloque {puts 'Hola desde el bloque'}

El resultado es:

Bloques

Si proporcionas un bloque a la llamada a un método, dentro del método puedes pasar el control de la ejecución del código del método al bloque. Es decir, suspender la ejecución del código del método, ejecutar el código del bloque y regresar el control al cuerpo del método después de llamar yield.

Puedes pasar parámetros a yield, que serán pasados al bloque. Dentro del bloque, debes enlistar los nombres de los argumentos entre barras verticales (|).

Observa el programa p023bloques2:

1 # Puedes agragar parametros a la llamada a yield: 
2 # que seran pasados al bloque
3 def llama_bloque
4   yield('hola', 99)
5 end
6 llama_bloque {|cad, num| puts cad + ' ' + num.to_s}

El resultado es:

Bloque2

Nota que el código del bloque no es ejecutado en el momento en que es encontrado por el el intérprete. Ruby recuerda el contexto en el que el bloque aparece y luego llama al método.

El valor que regresa un bloque (como el de un método) es el valor de la última expresión evaluada. Este valor de retorno está entonces disponible dentro del método como el valor de regreso de la llamada a yield.

Los bloques no son objetos pero pueden ser convertidos a objetos de la clase Proc. Esto puede lograrse llamando al método lambda del módulo Kernel. Un bloque creado con lambda actúa como un método en Ruby. Si no especificas el número correcto de argumentos, no puedes llamar al bloque.

1 prc = lambda {'hola'}

Los objetos de clase Proc son bloques de código que han sido relacionados con un conjunto de variables. La clase Proc tiene un método llamado call que invoca al bloque. El programa p024proccall.rb ilustra lo anterior:

 1 # Los bloques no son objetos
 2 # pueden ser convertidos a objetos de la clase Proc con el metodo lambda
 3 prc = lambda {puts 'Hola'}
 4 # el metodo call invoca al bloque
 5 prc.call
 6 
 7 # otro ejemplo
 8 brindis = lambda do
 9   puts 'Salud'
10 end
11 brindis.call

El resultado es:

Procs

Recuerda que no puedes pasar métodos a otros métodos, (pero puedes pasar procs) y que los métodos no pueden regresar otros métodos (pero pueden regresar procs).

El siguiente ejemplo muestra como los métodos pueden aceptor procs p025mtdproc.rb

 1 def un_metodo(un_proc)
 2   puts 'Inicio del metodo'
 3   un_proc.call
 4   puts 'Fin del metodo'
 5 end
 6 
 7 saluda = lambda do
 8   puts 'Hola'
 9 end
10 
11 un_metodo saluda

El resultado es:

Procs2

Este es otro ejemplo de pasar argumentos usando lambda:

1 un_bloque = lambda {|x| puts x}
2 un_bloque.call "Hola Mundo!"
3 
4 #el resultado es: Hola Mundo!

Fabio Akita, un entusiasta de Rails brasileño también conocido como “AkitaOnRails” escribió un artículo acerca de los bloques Ruby para los miembros de rubylearning.com.