Bloques en Ruby: yield, do…end, proc y lambda
En Ruby, yield es la palabra con la que un método ejecuta el bloque que le han pasado. Un bloque es código que le das a un método para que lo ejecute cuando le convenga. Es como pedirle a quien cuida de tu casa: «cuando riegues las plantas, haz también esto». El método sabe cuándo; tú le dices qué.
Lo usas constantemente sin darte cuenta: en each, times, map o loop, lo que va entre llaves o entre do y end es un bloque.
Todo el código de este artículo se ha ejecutado con Ruby 4.0.9 (imagen oficial ruby:latest de Docker). Cada ejemplo va seguido de su salida real.
yield: cómo un método ejecuta tu bloque
yield significa «ahora le toca al bloque»: el método hace su parte y, en el punto donde escribe yield, cede el control al código que le has pasado.
def saludar
puts "¡Hola!"
yield
end
saludar do
puts "Hola desde el bloque"
end
¡Hola!
Hola desde el bloque
El método puede tener sus propios parámetros y además recibir el bloque. El bloque no cuenta como parámetro: va aparte, detrás de los paréntesis.
def saludar_a(nombre)
puts "¡Hola, #{nombre}!"
yield
end
saludar_a("Pepe") { puts "Hola desde el bloque" }
¡Hola, Pepe!
Hola desde el bloque
Darle datos al bloque
Lo que pongas detrás de yield llega a los parámetros del bloque, los que van entre barras (|hora, nombre|), en el mismo orden:
def preparar_cita
yield 4, "Pepe"
end
preparar_cita do |hora, nombre|
puts "¿Qué pasó, #{nombre}? Son las #{hora}"
end
¿Qué pasó, Pepe? Son las 4
Recibir lo que devuelve el bloque
yield devuelve el valor de la última línea del bloque, así que el método puede usarlo:
def doble
yield(21) * 2
end
p doble { |n| n }
42
yield, el control va del método al bloque con un valor y vuelve con el de su última línea.yield varias veces
Un método puede llamar a yield tantas veces como quiera. Así funcionan por dentro each y times: recorren algo y, en cada vuelta, ceden el control al bloque con un valor distinto.
def tres_veces
yield 1
yield 2
yield 3
end
tres_veces { |n| puts "Vuelta #{n}" }
Vuelta 1
Vuelta 2
Vuelta 3
Dos formas de escribir un bloque: llaves y do … end
Llaves, para una línea
contactos = { "Ana" => "600111222", "Luis" => "600333444" }
contactos.each { |nombre, telefono| puts "#{nombre}: #{telefono}" }
Ana: 600111222
Luis: 600333444
do … end, para varias líneas
loop do
puts "¿Quieres otra vuelta? s/n"
respuesta = gets.chomp
break if respuesta == "n"
puts "Volvemos a empezar"
end
Este ejemplo lee del teclado. Ejecutado respondiendo s y después n:
¿Quieres otra vuelta? s/n
Volvemos a empezar
¿Quieres otra vuelta? s/n
Los parámetros del bloque funcionan igual con las dos formas:
3.times do |vuelta|
puts "Empiezo la vuelta #{vuelta}"
puts "Termino la vuelta #{vuelta}"
end
Empiezo la vuelta 0
Termino la vuelta 0
Empiezo la vuelta 1
Termino la vuelta 1
Empiezo la vuelta 2
Termino la vuelta 2
La convención es usar llaves para una línea y do … end para varias. La diferencia es de estilo salvo en un detalle: las llaves se unen con más fuerza a lo que tienen al lado. Si llamas a un método sin paréntesis, do … end va al método de fuera:
puts [1, 2, 3].map { |n| n * 2 }
puts [1, 2, 3].map do |n|
n * 2
end
2
4
6
#<Enumerator:0x00007b6fb18bdb60>
En la primera línea, las llaves van con map. En la segunda, el bloque do … end se lo lleva puts, que lo ignora, y map sin bloque devuelve un enumerador (la dirección que acompaña a Enumerator cambia en cada ejecución). Ante la duda, usa paréntesis: puts([1, 2, 3].map do |n| n * 2 end).
¿Me han pasado un bloque? block_given?
Si llamas a yield y nadie ha pasado un bloque, Ruby lanza un error:
def saludar
yield
end
begin
saludar
rescue LocalJumpError => error
puts "LocalJumpError: #{error.message}"
end
LocalJumpError: no block given (yield)
Para evitarlo, pregunta antes con block_given?, que devuelve true si al método que se está ejecutando le han pasado un bloque:
def hablar(nombre)
if block_given?
yield(nombre)
else
"Soy #{nombre} y estoy hablando"
end
end
puts hablar("Pepe")
puts hablar("Pepe") { |texto| "#{texto} habla lo que yo diga" }
Soy Pepe y estoy hablando
Pepe habla lo que yo diga
El patrón de la librería estándar: sin bloque, un enumerador
Muchos métodos de Ruby, como each o map, hacen algo útil aunque no les pases bloque: devuelven un enumerador, un objeto que recorre los valores cuando se lo pidas. Puedes hacer lo mismo en tus métodos combinando block_given? con to_enum:
def cada_par(numeros)
return to_enum(:cada_par, numeros) unless block_given?
numeros.each { |n| yield n if n.even? }
end
cada_par([1, 2, 3, 4]) { |n| puts "Par: #{n}" }
p cada_par([1, 2, 3, 4]).to_a
Par: 2
Par: 4
[2, 4]
Recibir el bloque como objeto: &block
Si pones & delante del último parámetro, Ruby convierte el bloque en un objeto Proc que puedes guardar, pasar a otro método o ejecutar con call. Por convención se suele llamar &block, pero el nombre es libre. Si no te han pasado bloque, vale nil:
def suma(a, b, &bloque)
resultado = a + b
if bloque
bloque.call(resultado)
else
puts "El cálculo es #{resultado}"
end
end
suma(4, 8) { |r| puts "El resultado es #{r}" }
suma(4, 8)
El resultado es 12
El cálculo es 12
Pasar el bloque a otro método
El uso más frecuente de &block es reenviar el bloque. Dentro del método, &bloque vuelve a convertir el objeto en bloque para el siguiente:
def registrar(mensaje, &bloque)
puts "Antes: #{mensaje}"
resultado = medir(&bloque)
puts "Después: #{mensaje}"
resultado
end
def medir
inicio = Process.clock_gettime(Process::CLOCK_MONOTONIC)
valor = yield
duracion = Process.clock_gettime(Process::CLOCK_MONOTONIC) - inicio
puts "Ha tardado menos de un segundo: #{duracion < 1}"
valor
end
p registrar("sumar") { 2 + 2 }
Antes: sumar
Ha tardado menos de un segundo: true
Después: sumar
4
Desde Ruby 3.1, si solo vas a reenviar el bloque, puedes dejarlo sin nombre con un & suelto:
def pasar_bloque(&)
[1, 2, 3].map(&)
end
p pasar_bloque { |n| n * 2 }
[2, 4, 6]
Cuándo yield y cuándo &block
yield
- Ejecuta el bloque directamente
- Es la opción que recomienda la documentación cuando solo vas a llamarlo
- Para la mayoría de los casos
&block
- Convierte el bloque en un objeto
Proc - Puedes guardarlo o pasarlo a otro método
- Cuando el bloque tiene que viajar
El método decide cuándo; el bloque decide qué.
Bloques, procs y lambdas: la diferencia
Un bloque no es un objeto: solo existe pegado a la llamada de un método. Cuando lo recibes con &block, Ruby lo convierte en un Proc. Pero también puedes crear ese objeto directamente, sin método de por medio, y guardarlo en una variable. Hay dos sabores: proc y lambda. Los dos son de la clase Proc; lambda? te dice cuál tienes.
saludo = proc { |nombre| "Hola, #{nombre}" }
doble = lambda { |n| n * 2 }
triple = ->(n) { n * 3 }
puts saludo.call("Ana")
puts doble.call(21)
puts triple.(5)
puts triple[5]
p saludo.lambda?
p doble.lambda?
p triple.class
Hola, Ana
42
15
15
false
true
Proc
->(n) { … } es la forma corta de escribir una lambda. Para ejecutarlos, call, .() y [] hacen lo mismo; call es la más explícita.
Según la documentación de Proc, las diferencias entre los dos sabores son dos: cómo tratan los argumentos y qué hace return.
Diferencia 1: los argumentos (la aridad)
La aridad es el número de argumentos que espera algo. Una lambda la exige como un método: si le faltan o le sobran, lanza ArgumentError. Un proc es tolerante: rellena con nil lo que falta, ignora lo que sobra y, si recibe un único array y tiene varios parámetros, lo reparte entre ellos.
mi_proc = proc { |a, b| "a=#{a.inspect} b=#{b.inspect}" }
mi_lambda = lambda { |a, b| "a=#{a.inspect} b=#{b.inspect}" }
puts mi_proc.call(1)
puts mi_proc.call(1, 2, 3)
puts mi_proc.call([1, 2])
p mi_proc.arity
p mi_lambda.arity
begin
mi_lambda.call(1)
rescue ArgumentError => error
puts "ArgumentError: #{error.message}"
end
a=1 b=nil
a=1 b=2
a=1 b=2
2
2
ArgumentError: wrong number of arguments (given 1, expected 2)
Los dos dicen que esperan dos argumentos (arity es 2), pero solo la lambda lo hace cumplir. Esa tolerancia del proc es la misma que tienen los bloques: por eso contactos.each { |nombre, telefono| … } funciona aunque each le entregue cada par como un array.
Diferencia 2: qué hace return
En una lambda, return sale de la lambda y el método que la llamó sigue. En un proc, return sale del método en el que se escribió el proc, y lo que quedaba del método no se ejecuta:
def con_lambda
comprobar = lambda { return "sale de la lambda" }
resultado = comprobar.call
"el método sigue (la lambda devolvió: #{resultado})"
end
def con_proc
comprobar = proc { return "sale del método" }
comprobar.call
"esta línea no se ejecuta"
end
puts con_lambda
puts con_proc
el método sigue (la lambda devolvió: sale de la lambda)
sale del método
Si quieres terminar un proc o un bloque sin salir del método, usa next:
def con_proc_y_next
comprobar = proc { next "next sale solo del proc" }
resultado = comprobar.call
"el método sigue (#{resultado})"
end
puts con_proc_y_next
el método sigue (next sale solo del proc)
Y si el método donde nació el proc ya ha terminado, su return no tiene adónde volver y Ruby lanza LocalJumpError:
def fabricar_proc
proc { return "demasiado tarde" }
end
huerfano = fabricar_proc
begin
huerfano.call
rescue LocalJumpError => error
puts "LocalJumpError: #{error.message}"
end
LocalJumpError: unexpected return
Resumen: bloque, proc y lambda
La tabla es un resumen nuestro de la documentación de Proc y de los ejemplos de arriba:
| Bloque | proc |
lambda |
|
|---|---|---|---|
| Es un objeto | No; se convierte en Proc con &block |
Sí, Proc |
Sí, Proc |
lambda? |
false al convertirlo |
false |
true |
| Argumentos que faltan o sobran | Tolerante | Tolerante | ArgumentError |
return |
Sale del método donde se escribió | Sale del método donde se escribió | Sale solo de la lambda |
| Se ejecuta con | yield o block.call |
call |
call |
Convertir un proc o una lambda en bloque
El & también funciona al revés: delante de un proc o una lambda en una llamada, lo pasa como bloque. Y como todos son objetos que recuerdan las variables que había donde se crearon (lo que se llama un closure), pueden modificarlas:
def ejecutar_dos_veces
yield
yield
end
contador = 0
sumar = proc { contador += 1 }
ejecutar_dos_veces(&sumar)
p contador
p [1, 2, 3].map(&->(n) { n * 10 })
2
[10, 20, 30]
Ese «recordar» permite fabricar funciones con memoria propia:
def crear_contador
cuenta = 0
-> { cuenta += 1 }
end
siguiente = crear_contador
siguiente.call
siguiente.call
p siguiente.call
3
Cuándo usar cada uno
Esto es una pauta nuestra, basada en las diferencias de arriba:
- Un bloque con
yieldpara el caso habitual: un método que hace algo y deja que quien lo llama decida un paso. &blockcuando el método tiene que guardar el bloque o pasárselo a otro.- Una lambda cuando quieres guardar una función en una variable o en un hash y que se comporte como un método: con los argumentos comprobados y un
returnque no te sorprenda. - Un proc cuando quieres la tolerancia de un bloque. Fuera de eso, la lambda da menos sorpresas.
Bloques en métodos de Ruby que ya usas
Muchos métodos de la librería estándar aceptan un bloque opcional. Un ejemplo curioso es Array#delete: si el elemento no existe devuelve nil, pero si le pasas un bloque, devuelve lo que diga el bloque:
numeros = [1, 2, 3]
p numeros.delete(5) { "no existe" }
p numeros.delete(5)
p numeros.delete(2)
p numeros
"no existe"
nil
2
[1, 3]
Otros que verás a diario:
%w[a b c].each_index { |i| puts "#{i}.-" }
p [1, 2, 3].map { |n| n * 10 }
p [1, 2, 3].map(&:to_s)
0.-
1.-
2.-
[10, 20, 30]
["1", "2", "3"]
&:to_s es un atajo: el & convierte el símbolo :to_s en un proc que llama a ese método, así que equivale a { |n| n.to_s }.
Parámetro implícito: it (Ruby 3.4 y posteriores)
Cuando el bloque tiene un solo parámetro, desde Ruby 3.4 puedes no nombrarlo y usar it:
p [1, 2, 3].map { it * 10 }
p [1, 2, 3].map { _1 * 10 }
[10, 20, 30]
[10, 20, 30]
Desde Ruby 2.7 existe _1, que sigue funcionando. Úsalos en bloques cortos; si el bloque tiene varias líneas, un nombre claro se lee mejor.
Errores frecuentes con bloques
LocalJumpError: no block given (yield). Has llamado ayieldsin que nadie pasara un bloque. Comprueba conblock_given?o devuelve un enumerador.- El bloque
do … endno llega a tu método. Pasa cuando la llamada no lleva paréntesis y hay otro método delante, comoputs. Usa paréntesis o llaves. - Un
returndentro de un bloque o un proc que corta el método. Si solo querías dar un valor y seguir, usanexto una lambda. ArgumentErroral llamar a una lambda. La lambda exige el número exacto de argumentos; un proc no.LocalJumpError: unexpected return. Un proc conreturnque se ejecuta cuando su método ya ha terminado.
Sigue aprendiendo
Para más ejemplos de bloques con each, times y compañía, mira Bucles e iteradores en Ruby. Los métodos que reciben bloques, con sus parámetros y su return, están en Métodos en Ruby. Y map con bloque es la forma habitual de transformar listas en Arrays en Ruby.
Fuentes
Consultadas el 9 de noviembre de 2026, en la documentación oficial de Ruby 4.0:
- Métodos: argumento de bloque,
&anónimo yyieldy llamadas a métodos con bloque, donde se explica la diferencia de precedencia entre llaves ydo … end. - Proc: formas de crear un proc o una lambda, semántica de lambda y no lambda (argumentos,
return,breakynext), proc huérfano yLocalJumpError,arity,lambda?, conversión con&yto_proc,_1eit. - Kernel#block_given?.
- Array#delete.
- NEWS de Ruby 2.7: parámetros numerados (
_1). - NEWS de Ruby 3.1: bloque anónimo
&. - NEWS de Ruby 3.4: introducción de
it.
Todos los ejemplos se han ejecutado con Ruby 4.0.9 (imagen oficial ruby:latest de Docker) y las salidas son las reales. El de loop y gets se ha ejecutado pasándole s y n por la entrada estándar.