O que é um bloco em Ruby
Blocos são trechos de código que você pode “anexar” a uma chamada de método. Eles não são objetos por padrão, mas podem ser capturados e transformados em objetos (Proc/lambda). Um bloco pode ser escrito com { ... } (mais comum em uma linha) ou com do ... end (mais legível em múltiplas linhas).
# { ... } costuma ser usado para blocos curtos
[1, 2, 3].map { |n| n * 2 }
# do ... end costuma ser usado para blocos maiores
[1, 2, 3].map do |n|
n * 2
endO bloco pode receber parâmetros (os “parâmetros do bloco”), declarados entre barras verticais | |. Esses parâmetros são preenchidos por quem “chama” o bloco (normalmente o método que faz yield).
Passando blocos para métodos: yield e block_given?
Um método pode executar o bloco associado usando yield. Se nenhum bloco foi passado, chamar yield levanta erro. Para evitar isso, use block_given?.
Executando um bloco com yield
def executar
yield
end
executar { puts "rodou" }Passando argumentos para o bloco
def com_valor
yield(10)
end
com_valor { |n| puts n * 3 } # 30Protegendo com block_given?
def executar_se_tiver_bloco
return :sem_bloco unless block_given?
yield
:ok
end
executar_se_tiver_bloco # :sem_bloco
executar_se_tiver_bloco { puts "agora sim" } # imprime e retorna :okyield múltiplas vezes
Você pode chamar o bloco várias vezes, o que é útil para APIs de repetição, tentativas, medições, etc.
def duas_vezes
yield
yield
end
duas_vezes { puts "oi" }Capturando o bloco como parâmetro (&block)
Além de usar yield, você pode capturar o bloco como um objeto do tipo Proc usando um parâmetro especial com &. Isso permite armazenar, passar adiante e chamar o bloco com call.
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
def executar_com_call(&block)
return :sem_bloco unless block
block.call
:ok
end
executar_com_call { puts "via call" }Quando capturado, o bloco vira um Proc. Isso é útil quando você quer repassar o bloco para outro método, ou quando quer chamar em momentos diferentes.
Repassando o bloco para outro método
def camada_externa(&block)
camada_interna(&block)
end
def camada_interna
yield("mensagem")
end
camada_externa { |txt| puts txt.upcase }Convertendo Proc em bloco e bloco em Proc com &
O operador & faz conversões importantes:
- Bloco → Proc: ao capturar com
&blockno parâmetro do método. - Proc → bloco: ao passar um Proc precedido de
¶ um método que espera bloco.
dobrar = Proc.new { |n| n * 2 }
resultado = [1, 2, 3].map(&dobrar)
# resultado: [2, 4, 6]Esse padrão é muito comum para reutilizar lógica de transformação/validação.
Proc e lambda: diferenças práticas
Em Ruby, tanto Proc.new quanto lambda criam objetos “chamáveis” (callables). As diferenças mais importantes no dia a dia são: aridade (como lidam com número de argumentos) e retorno (como o return se comporta).
Aridade (quantidade de argumentos)
lambda é mais rígida: exige a quantidade correta de argumentos. Proc é mais flexível: aceita menos (preenche com nil) e ignora extras em muitos casos.
p1 = Proc.new { |a, b| [a, b] }
l1 = lambda { |a, b| [a, b] }
p1.call(1) # [1, nil]
# p1.call(1, 2, 3) geralmente não falha e ignora extra (depende do caso)
l1.call(1) # ArgumentError: wrong number of arguments
l1.call(1, 2) # [1, 2]return dentro de Proc vs lambda
lambda: return retorna da própria lambda. Proc: return tenta retornar do método onde o Proc foi definido (o que pode encerrar o método “por fora” ou até gerar erro se não houver contexto adequado).
def com_lambda
l = lambda { return :da_lambda }
x = l.call
:do_metodo
end
com_lambda # :do_metododef com_proc
p = Proc.new { return :do_metodo }
p.call
:nao_chega_aqui
end
com_proc # :do_metodoNa prática: use lambda quando quiser comportamento mais “parecido com método” (aridade rígida e retorno local). Use Proc quando quiser flexibilidade e estiver ciente do impacto do return.
Checagem rápida: lambda?
l = lambda { }
p = Proc.new { }
l.lambda? # true
p.lambda? # falseAPIs simples baseadas em blocos (padrões úteis)
1) Medindo tempo de execução com bloco
Um padrão clássico é criar um método que recebe um bloco, executa e mede duração. O passo a passo é: (1) capturar o tempo inicial, (2) executar o bloco, (3) capturar o tempo final, (4) retornar duração e/ou resultado.
def medir_tempo(rotulo = "execução")
inicio = Process.clock_gettime(Process::CLOCK_MONOTONIC)
resultado = yield
fim = Process.clock_gettime(Process::CLOCK_MONOTONIC)
duracao = fim - inicio
{ rotulo: rotulo, segundos: duracao, resultado: resultado }
end
info = medir_tempo("soma") do
(1..1_000_000).reduce(0) { |acc, n| acc + n }
end
# info[:segundos] -> duração
# info[:resultado] -> valor calculadoNote que o método retorna um Hash com metadados e o resultado do bloco, o que facilita uso em logs e testes.
2) Garantindo limpeza com ensure (padrão “around”)
Quando você quer garantir que algo aconteça mesmo se o bloco falhar, use ensure. Passo a passo: (1) preparar recurso, (2) executar bloco, (3) garantir liberação/limpeza.
def com_recurso(nome)
puts "abrindo: #{nome}"
yield
ensure
puts "fechando: #{nome}"
end
com_recurso("arquivo.txt") do
puts "processando..."
end3) Validações com bloco (mini DSL)
Você pode criar uma API onde o bloco descreve regras. Um jeito simples é: (1) criar um objeto validador, (2) executar o bloco passando esse objeto, (3) coletar erros.
class Validador
attr_reader :erros
def initialize
@erros = []
end
def exigir(condicao, mensagem)
@erros << mensagem unless condicao
end
end
def validar
v = Validador.new
yield(v)
v
end
usuario = { nome: "", idade: 15 }
v = validar do |val|
val.exigir(!usuario[:nome].strip.empty?, "nome é obrigatório")
val.exigir(usuario[:idade] >= 18, "idade mínima é 18")
end
# v.erros -> ["nome é obrigatório", "idade mínima é 18"]Esse padrão é a base para criar “mini linguagens” internas (DSLs) com blocos, mantendo o código de chamada expressivo.
4) Transformações configuráveis (pipeline com bloco opcional)
Às vezes você quer um comportamento padrão, mas permitir customização via bloco. Use block_given? para escolher entre o padrão e o customizado.
def formatar_lista(itens)
if block_given?
itens.map { |item| yield(item) }
else
itens.map { |item| item.to_s }
end
end
formatar_lista([1, 2, 3])
# ["1", "2", "3"]
formatar_lista([1, 2, 3]) { |n| "n=#{n}" }
# ["n=1", "n=2", "n=3"]Exercícios práticos (para treinar blocos, yield, Proc e lambda)
Exercício 1: timer com retorno do bloco
Crie um método timer que receba um rótulo e um bloco. Ele deve retornar um Hash com: :rotulo, :ms (milissegundos) e :resultado (o retorno do bloco). Requisitos: usar Process.clock_gettime e yield.
# Assinatura sugerida
# def timer(rotulo = "tarefa")
# ...
# endExercício 2: método retry simples baseado em bloco
Implemente tentar(vezes) que executa o bloco e, se ocorrer uma exceção, tenta novamente até acabar. Ao final, se não conseguir, relança a última exceção. Dica: use begin/rescue e um contador.
# Exemplo de uso esperado:
# tentar(3) do
# pode_falhar
# endExercício 3: validação com DSL
Expanda o Validador para ter métodos: exigir_presenca(valor, campo) e exigir_minimo(numero, minimo, campo). Faça o bloco ficar assim:
# validar do |v|
# v.exigir_presenca(usuario[:nome], :nome)
# v.exigir_minimo(usuario[:idade], 18, :idade)
# endExercício 4: Proc vs lambda na prática
Crie dois callables: um Proc e uma lambda, ambos esperando dois argumentos. Teste chamadas com 1 e com 3 argumentos e registre o comportamento. Depois, crie um método executar_callable(callable) que chama o objeto com call e trate o caso de ArgumentError para a lambda.
# Sugestão:
# def executar_callable(callable)
# callable.call(1)
# rescue ArgumentError => e
# e.message
# endExercício 5: aceitando Proc como bloco
Crie um método aplicar(itens, &transformacao) que aplica a transformação em cada item. Depois, crie uma transformação como Proc e passe com &.
# Exemplo:
# up = Proc.new { |s| s.upcase }
# aplicar(["a", "b"], &up) # ["A", "B"]