Subsecciones


Argumentos Bloque

Una de las características de los bloques es su carácter anónimo. Tampoco son pasados al método en la forma tradicional: no figuran en la lista de argumentos. En vez de eso, son invocados por medio de yield. Por ejemplo:

~/rubytesting$ cat -n iter.rb 
     1  def seq(n,m,c)
     2    i=0
     3    while (i < n)
     4      yield i*m+c
     5      i += 1
     6    end
     7  end
     8  
     9  seq(5,2,2) { |x| puts x }
Que usamos así:
~/rubytesting$ ruby iter.rb 
2
4
6
8
10

Si se prefiere tener un mayor control sobre el bloque (por ejemplo, se desea pasar el bloque como argumento a otro método) lo añadiremos como argumento final a nuestro método, prefijado por un ampersand &. Dentro del método, el argumento asociado con el bloque pertenecerá a la clase Proc y se podrá invocar usando el método call del objeto Proc en vez de yield:

~/rubytesting$ cat -n iter2.rb 
     1  def seq(n,m,c, &b)
     2    i=0
     3    while (i < n)
     4      b.call(i*m+c)
     5      i += 1
     6    end
     7  end
     8  
     9  seq(5,2,2) { |x| puts x }
Nótese que el asunto afecta a la definición del método pero que la forma de la llamada (línea 9) no cambia.

Ejecución:

~/rubytesting$ ruby iter2.rb 
2
4
6
8
10


LLamada con Paso de Objetos Proc de Forma Explícita

También podemos contruir un objeto Proc y pasarlo explícitamente al método. En este caso se trata de un objeto ordinario y ni usamos el & ni la llamada tiene la sintáxis con bloque:

# This version expects an explicitly-created Proc object, not a block
def sequence4(n, m, c, b)  # No ampersand used for argument b
  i = 0
  while(i < n)
    b.call(i*m + c)        # Proc is called explicitly
    i += 1
  end
end

p = Proc.new {|x| puts x }  # Explicitly create a Proc object 
sequence4(5, 2, 2, p)       # And pass it as an ordinary argument


Los Argumentos Bloque Prefijados con & Deben Ser los Últimos

Los siguientes dos métodos son legales:

def sequence5(args, &b) # Pass arguments as a hash and follow with a block
  n, m, c = args[:n], args[:m], args[:c]
  i = 0
  while(i < n)
    b.call(i*m + c)
    i += 1
  end
end

# Expects one or more arguments, followed by a block
def max(first, *rest, &block) 
  max = first
  rest.each {|x| max = x if x > max }
  block.call(max)
  max
end

  1. Vale la pena resaltar que la sentencia yield también funciona sobre un parámetro definido con &.

  2. Podemos invocarlo con yield incluso si el parámetro ha sido convertido a un objeto Proc y pasado como un argumento.


Utilizando & en la Invocación de Métodos

& como Operador que convierte un Proc en un Bloque

Sintáxis de la llamada

El Proc construido a partir del bloque se pasa como el último argumento en la llamada al método separándolo por comas del resto de los argumentos:
ruby-1.9.2-head :008 > def f(a,b); yield a+b; end
 => nil 
ruby-1.9.2-head :009 > f(1,2) { |x| puts x }
3
 => nil
No podemos llamarlo como si fuera el bloque que sigue:
ruby-1.9.2-head :010 > qq = lambda { |x| puts x }
 => #<Proc:0x007f93e387d3d0@(irb):10 (lambda)> 
ruby-1.9.2-head :011 > f(1,2) &qq
LocalJumpError: no block given (yield)
Así tampoco:
ruby-1.9.2-head :012 > f(1,2, qq)
ArgumentError: wrong number of arguments (3 for 2)
Es necesario hacer la conversión con & y pasarlo como último argumento:
ruby-1.9.2-head :013 > f(1,2, &qq)
3
 => nil

Usando & sobre el Nombre de un Método

Realmente, la invocación con & puede usarse con cualquier objeto que disponga de un método to_proc.

En Ruby 1.9 la clase Symbol dispone de un método to_proc, lo que permite a los símbolos ser prefijados con & y ser pasados a los iteradores:

pry(main)> words = ['and', 'but', 'car']     # An array of words
=> ["and", "but", "car"]
pry(main)> uppercase = words.map &:upcase    # Convert to uppercase with String.upcase
=> ["AND", "BUT", "CAR"]
Este código es equivalente a:
upper = words.map {|w| w.upcase } # This is the equivalent code with a block

Casiano Rodriguez León 2015-06-18