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.

9 Comentários to “Blocos em Ruby( lambda, proc, yield e tudo mais)”

  1. Junio Vitorino diz:

    Du caralho mano, parabéns curti pakas o tutorial agora entendo porque do yield nas views. :P

  2. Juarez P. A. Filho diz:

    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.

  3. Mayron Cachina diz:

    Muito interessante, vai ajudar bastante nos estudos!!! Estou passando a acompanhar o blog!

    []´s

  4. Daniel Lopes diz:

    Obrigado pessoal, da próxima vou tentar falar de class-eval e como ela pode quebrar a idéia de closures do ruby.

  5. Davis Zanetti Cabral diz:

    Parabéns pelo artigo. Muito bem organizado e as idéias mto bem apresentadas!

  6. Silvio Fernandes diz:

    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

  7. Herminio Torres diz:

    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.

  8. Ricardo Amorim diz:

    Parabéns, ótimo Post.

  9. daniel lopes diz:

    @Herminio, o collect executa o bloco para cada item do array e retorna um novo array com o valor retornado de cada iteração.

Deixe um comentário

If you can read this, you don't use a typical webbrowser that plays nice with CSS.
Please do not fill in anything here!