Blocos e Iteradores em Ruby: Crie o Seu
Entenda como métodos Ruby recebem blocos, passam valores com yield e devolvem um Enumerator quando não há bloco. Crie e execute seu próprio iterador.

Nesta página
Blocos e Iteradores em Ruby: Crie o Seu
Você talvez já tenha usado numeros.each { |numero| puts numero }. Mas o que acontece dentro do método? Como each entrega um número ao seu bloco? E se ninguém passar um bloco?
Vamos criar um iterador pequeno para enxergar os dois lados. Se each, map e select ainda forem novidade, comece pelo guia de arrays em Ruby. Os exemplos abaixo foram executados com Ruby 3.3.

O método escolhe quando executar o bloco
Um bloco é um trecho de código ligado à chamada de um método. Os parâmetros entre | recebem valores do método. Dentro dele, yield chama o bloco:
1def duas_vezes
2 yield 1
3 yield 2
4end
5
6duas_vezes do |numero|
7 puts "Visita #{numero}"
8endVisita 1
Visita 2Quem chama fornece o bloco; duas_vezes controla quando ele roda e quais valores recebe. do ... end funciona bem para várias linhas. Para uma chamada curta, também podemos usar chaves: duas_vezes { |numero| puts numero }.
O valor de uma expressão yield é o retorno do bloco. Um método pode aproveitar esse valor, como neste pequeno transformador:
1def transformar_dois
2 [yield(1), yield(2)]
3end
4
5p transformar_dois { |numero| numero * 10 }[10, 20]Essa é a ideia por trás de transformações como map: o método reúne os valores devolvidos pelo bloco. Um iterador também pode apenas entregar valores e ignorar os retornos do bloco.
Crie um iterador para notas aprovadas
Imagine um relatório que precise visitar somente notas a partir de um mínimo escolhido. cada_aprovada decide quais notas entregar ao bloco:
1def cada_aprovada(notas, minimo)
2 return enum_for(__method__, notas, minimo) unless block_given?
3
4 notas.each do |nota|
5 yield nota if nota >= minimo
6 end
7end
8
9notas = [48, 82, 67, 91]
10cada_aprovada(notas, 70) { |nota| puts "Aprovada: #{nota}" }Aprovada: 82
Aprovada: 91Leia o método de cima para baixo:
block_given?pergunta se a chamada recebeu um bloco.- Com um bloco,
notas.eachpercorre a entrada em ordem. A condição decide se a nota chega aoyield. - O bloco de quem chamou imprime cada nota recebida. O método não reúne os valores retornados pelo bloco. Com um bloco, esta implementação devolve
notas, poisnotas.eachdevolve o próprio array.

A seta de volta importa: depois que o bloco roda para 82, cada_aprovada retoma o laço e pode entregar 91. O bloco não assume toda a iteração; o método continua decidindo quais valores chegam e quando.
minimo é um argumento comum do método. O bloco acrescenta o comportamento escolhido por quem o chamou. O método e o array não precisam saber se as notas serão impressas, contadas ou guardadas.
Sem bloco? Devolva algo útil
Sem a primeira linha de cada_aprovada, chegar a yield sem bloco causaria LocalJumpError. Em vez disso, podemos devolver um Enumerator, para que a pessoa escolha depois como consumir os valores:
1aprovadas = cada_aprovada([48, 82, 67, 91], 70)
2
3p aprovadas.class
4p aprovadas.to_a
5p aprovadas.firstEnumerator
[82, 91]
82enum_for(__method__, notas, minimo) cria um enumerador para este método e guarda seus argumentos. Quando to_a ou first pede valores, o enumerador chama cada_aprovada com um bloco. Ele não monta o array filtrado no momento da atribuição a aprovadas. Cada consumo executa o método novamente; portanto, mudanças no array original podem alterar resultados futuros.
Essa interface com duas formas de uso—entregar valores a um bloco ou devolver um enumerador—é comum nos métodos de coleções do Ruby. Se o seu método exigir um bloco, também é possível retirar a verificação e documentar essa exigência. O importante é escolher conscientemente.
Experimente
Escreva cada_palavra_longa(palavras, tamanho_minimo). O método deve entregar palavras com pelo menos esse tamanho e devolver um enumerador quando for chamado sem bloco. Teste cada_palavra_longa(%w[sol lontra elefante], 6).to_a antes de abrir a resposta.
1def cada_palavra_longa(palavras, tamanho_minimo)
2 return enum_for(__method__, palavras, tamanho_minimo) unless block_given?
3
4 palavras.each do |palavra|
5 yield palavra if palavra.length >= tamanho_minimo
6 end
7end
8
9p cada_palavra_longa(%w[sol lontra elefante], 6).to_a["lontra", "elefante"]A ideia central é simples: o bloco pertence a quem chama, e o método escolhe quando executá-lo. Depois de criar um método assim, os iteradores prontos do Ruby ficam muito mais fáceis de entender.
Para saber mais
Leia a seguir

std::vector em C++: Tamanho, Capacidade e Crescimento Seguro
Entenda como std::vector cresce em C++: a diferença entre tamanho e capacidade, quando usar reserve, por que resize é diferente e como evitar referências inválidas.

Como Tratar Erros em Go: Um Guia Prático
Aprenda a tratar erros em Go com exemplos executáveis: retorne um erro, confira o resultado, acrescente contexto com %w e reconheça um arquivo ausente com errors.Is.

O Que É a Linguagem de Programação Rust? Um Guia Simples da Linguagem Rápida Que Não Deixa Você Travar
Um guia simples e sem jargões sobre o Rust, a linguagem tão rápida quanto o C que impede famílias inteiras de erros de memória antes de o programa rodar. Entenda a ideia de posse, por que gigantes da tecnologia o adotam e onde ele é usado.
Comentários
Seja gentil e específico. Comentários com links de contas novas aparecem depois da análise de um moderador.