Iteradores e blocos
Iteradores são métodos que executam um bloco de código que lhes seja passado.
Blocos são delimitados por { ... }, ou do ... end
({} tem maior precedência), e podem receber argumentos, declarados
entre | ... |.
"abcdef".each_byte { |c| # O método each_byte vai executar o bloco para cada byte da string,
print c, " " # lhe passando cada byte pelo argumento c
}
Quando definindo iteradores, o bloco recebido é executado pelo comando
yield, e os argumentos passados a yield serão
atribuídos aos argumentos do bloco por atribuição múltipla.
class ListaEncadeada
...
def each
... # Passa por todos os elementos
yield(elemento) # executando o bloco recebido, com o elemento atual como argumento
end
end
lista = ListaEncadeada.new
...
lista.each { |e| print e }
Iteradores são muito úteis em substituição a laços, em muitos casos. Note que
blocos não são úteis apenas à iteradores. Eles podem ser passados a qualquer
método. Além disso, blocos podem ser convertidos em objetos Proc, e
dessa forma atribuídos a variáveis, passados como argumentos normais, etc..
def teste(um_bloco)
um_bloco.call
end
bloco = proc {
puts "oi!"
}
teste(bloco) # >> oi!
É importante destacar algo que pode ser fonte de confusão. O contexto de variáveis do bloco é o mesmo de onde ele foi criado. O bloco tem acesso às variáveis locais do bloco onde foi criado, por exemplo. Mas o bloco não introduz novas variáveis no contexto onde foi criado, apenas no seu próprio. Ou seja, variáveis já existentes podem ser usadas em um bloco (inclusive variáveis de instância, de classe, e globais), e variáveis de um bloco são locais ao bloco, caso não existem anteriormente.
l = "z"
m = "1"
["a", "b", "c"].each { |l|
print l # >> abc
m = "2"
n = l
}
puts l # >> c
puts m # >> 2
puts n # >> NameError: undefined local variable or method `n' ...