Read in english (Automatic translation wiht Google)
Seguindo a linha do último tutorial de ruby, hoje vamos falar de blocos. Vou tentar explicar de forma resumida e com exemplos o que são blocos e como usar. Lembro a primeira vez que vi um bloco em Ruby fiquei totalmente perdido pois ainda não tinha visto nada com uma sintaxe parecida em outras linguagens ( ex.: { |item| puts item } ). Blocos são confusos de entender a primeira vista pois acompanham um série de conceitos como closures, proc, lambda, Proc.new, yield …
Um bloco é um trecho de código Ruby que devem ser executado (dã). Melhorando a explicação com um exemplo:
1
2
3
4
5
6
|
["daniel","lopes"].each {|item| puts item}
# => resultado sera:
# => daniel
# => lopes
|
No exemplo acima, tenho um array e este array possui o método each. O método each recebe um bloco como parâmetro… ou seja, este bloco será executado a cada iteração em meu array.
Nosso bloco é formado por chaves que delimitam o inicio e o fim do bloco, um parâmetro ( |item| ) e sentença a ser executada ( puts item ). Também poderiamos delimitar o início e fim de nosso bloco com do end ao invés de {} , mas a convenção é que usamos {} para blocos de uma linha e blocos maiores com do end
No exemplo passamos o bloco para o método each e ele executou este bloco várias vezes, também optamos por passar um parâmetro para o bloco, mas podemos passar quantos parâmetros precisarmos. Vejamos agora o exemplo abaixo:
1
2
3
4
5
6
7
|
meu_hash = {:nome=>"daniel",:sobrenome=>"lopes"}
meu_hash.each {|index,item| puts "chave #{index},valor #{item}"}
# => resultado sera:
# chave nome,valor daniel
# chave sobrenome,valor lopes
|
Agora meu método each passa dois parâmetros para meu bloco, pois each agora pertence a um Hash e este é o comportamento do each em um hash, diferentemente de each em um Array, como vimos anteriormente.
Agora está claro que um bloco serve para armazenar quantas linhas de código quisermos, para ser executado em um certo momento e com seu próprio escopo. Portanto blocos são closures (fechamentos), pois eles estão fechados no ambiente em torno deles. Blocos também são chamados de nameless functions (funções sem nome).
Mas e se quisermos atribuir um bloco a uma variável? Não é possível, teremos um erro. Por definição blocos só são usados com métodos. Então para isso podemos usar o método lambda. O método lambda está presente no módulo Kernel do Ruby, (portanto não precisamos instancia nada quando o chamamos) e retorna um objeto da classe Proc, equivalente a Proc.new mas com algumas diferenças… confuso, né??? Então vamos ver um exemplo:
1
2
3
4
5
6
|
meu_bloco = lambda{ |param| puts "Hello #{param} !!!" }
meu_bloco.call("Daniel") #=> Hello Daniel !!!
meu_bloco.class #=> Proc
|
Então com lambda criamos uma instância da classe Proc. Proc objects são blocos de código vinculados a variáveis locais. Um Proc( procedures ) tem seu próprio escopo e pode ser chamado várias vezes em contextos diferentes e ainda ter acesso a estas variáveis. Então temos 3 formas de criar um Proc, com lambda, com proc (mesmo resultado que lambda) e com Proc.new … a diferença é que lambda e proc fazem checagem de parâmetro enquanto Proc.new não. Vamos ver um exemplo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
bloco1 = lambda{|x,y| x+y}
bloco1.call(1,2) # => 3
bloco1.call(1) # erro por numero incorreto de parametros
ArgumentError: wrong number of arguments (1 for 2)
from (irb):1
from (irb):3:in `call'
from (irb):3
bloco2 = Proc.new {|x,y| x+y}
bloco2.call(2) # nao checa os parametros e tenta fazer o calculo com nil
TypeError: nil can't be coerced into Fixnum
from (irb):7:in `+'
from (irb):7
from (irb):8:in `call'
from (irb):8
from :0
bloco2.call(2,3,4,5) #=> 5
# na linha acima ignorou 4 e 5 que foram parametros a mais.
# abaixo a mesma coisa, mas usando proc
bloco3 = proc {|x,y| x+y}
bloco3.call(2,3,4,5) # erro por parametros
ArgumentError: wrong number of arguments (4 for 2)
from (irb):10
from (irb):11:in `call'
from (irb):11
from :0
|
Acho que agora ficou claro a diferença entre lambda,proc e Proc.new … mas ainda falta uma coisa, como faço para criar um método que aceite blocos como parâmetro? Não precisamos fazer nada, basta passar o bloco mas devemos saber quando aquele bloco vai ser executado dentro do método, para isso temos o yield. Vejamos o exemplo abaixo.
1
2
3
4
5
6
7
8
9
10
11
|
def executa_blocos
puts "Olá, vou executar um bloco logo abaixo"
yield
puts "==============="
end
executa_blocos { puts "rodando bloco!" }
# => Olá, vou executar um bloco logo abaixo
# => rodando bloco!
# => ===============
|
O método yield espera um bloco, e no momento em que yield for chamado ele irá executar o bloco disponível. Agora um outro exemplo que pode gerar dúvidas:
1
2
3
4
5
6
7
8
9
|
def executa_blocos(vezes)
vezes.times {yield} if block_given?
end
executa_blocos(2) { puts "rodando bloco!" }
#=> rodando bloco!
#=> rodando bloco!
|
Então podemos passar parâmetros e também um bloco para um método. Mas ainda existe uma outra forma de passar um bloco como parâmetro, através do &, veja abaixo:
1
2
3
4
5
6
7
|
def executa_blocos(&meu_proc)
yield
end
executa_blocos{ puts "rodando bloco!" }
#=> rodando bloco!
|
Desta forma o método aceita um bloco como parâmetro e vai convertelo em um Proc, mas de forma que não precisamos chamar o método call para executar pois temos o yield.
Bem, acho que é isso… espero ter deixado bem claro o que são blocos, lambda, proc, Proc.new, yield e parâmetros com &. Talvez eu tenha me esquecido de alguma coisa ou falado alguma bobeira(não sou nenhum expert em blocos :D), de qualquer forma postem nos comentários.
22/09/2008 em 08:52 PM
Du caralho mano, parabéns curti pakas o tutorial agora entendo porque do yield nas views. :P
22/09/2008 em 10:39 PM
Daniel, gostei muito desse post… Eu estava justamente querendo aprender sobre isso, mas sabe como é a correria né? Foi muito esclarecedor e objetivo esse artigo. Valeu e até a próxima.
23/09/2008 em 01:24 PM
Muito interessante, vai ajudar bastante nos estudos!!! Estou passando a acompanhar o blog!
[]´s
23/09/2008 em 02:27 PM
Obrigado pessoal, da próxima vou tentar falar de class-eval e como ela pode quebrar a idéia de closures do ruby.
23/09/2008 em 03:46 PM
Parabéns pelo artigo. Muito bem organizado e as idéias mto bem apresentadas!
23/09/2008 em 09:08 PM
Olá,
Muito bom o tutorial, bem objetivo mesmo. Como estou iniciando foi de grande ajuda, inclusive vou rever os codigos aqui da aplicação que estou desenvolvendo, para melhorar o codigo utilizando esse novo conhecimento. Sucesso pra voce!!!
[]`s
04/11/2008 em 07:37 AM
Bem, se minha variável for um array ou um hash, eu posso fazer isso com o método collect, onde eu venha coletar o que eu desejo e o resultado jogar numa nova variável… num é possível? além do each, como foi citado ali acima, já que o mesmo não retorna nada.
04/11/2008 em 07:54 AM
Parabéns, ótimo Post.
04/11/2008 em 10:38 AM
@Herminio, o collect executa o bloco para cada item do array e retorna um novo array com o valor retornado de cada iteração.