Introdução

Seja bem-vindo de volta à série de tutoriais de Ruby on Rails.

Vamos fazer uma pausa na nossa aplicação para estudar alguns conceitos de programação em Ruby .

Ruby possuí vários recursos poderosos, sendo Block, Proc e Lambdas parte deles. Em resumo, esses recursos possibilitam passar código para um método e executar este código posteriormente. Apesar de usar tais recursos, muitos desenvolvedores não entendem perfeitamente as diferenças sutís entre eles.

Blocks

Um Block é código passado implicitamente a um método atravéz do uso de chaves, {…}, ou do … end, sendo convenção o uso de chaves para blocos de uma linha e do … end para blocos com mais de uma linha.

Por exemplo, os dois blocos a seguir têm a mesma funcionalidade:

[code language=”ruby” collapse=”false”]
array = [1,2,3,4,5]
array.map! do |n|
n + n
end
=> [2, 4, 6, 8, 10]

array = [1,2,3,4,5]
array.map! { |n| n + n }
=> [2, 4, 6, 8, 10]
[/code]

O segredo por trás de um bloco é a palavra-chave yield (ceder) , que permite que qualquer método possa ser chamado com um bloco como argumento implícito. Por exemplo, o seguinte código:

[code language=”ruby” collapse=”false”]
def bloco
puts ‘Inicio do método’
# Você pode chamar o método recebido como argumento com a palavra-chave yield
yield
yield
puts ‘Fim do método’
end
bloco {puts ‘Dentro do bloco’}
[/code]

Tem a seguinte saída:

[code collapse=”false”]
Início do método
Dentro do bloco
Dentro do bloco
Fim do método
[/code]

A palavra-chave “yield” também pode receber argumentos que são passados para o método a ser utilizado. Como nesse exemplo anterior do método “map!”:

[code language=”ruby” collapse=”false”]
class Array
def map!
self.each_with_index do |value, index|
self[index] = yield(value)
end
end
end
[/code]

Esta simples representação de “map!” chama o método “each_with_index” e substitui o valor em determinado índice pelo o resultado do bloco. Este exemplo, apesar de simples, serve para demonstrar o poder da palavra-chave “yield”.

Procs

O exemplo anterior mostra uma limitação dos blocos: eles são descartáveis. Precisamos reescrever blocos toda vez que formos utilizá-los em diferentes conjustos, mas podemos armazenar um bloco para uso posterior com o objeto Proc. Podemos armazená-lo numa variável e explicitamente passá-lo como argumento a qualquer método que aceite um objeto que possa ser chamado.

Reescrevendo o exemplo anterior do “map!” como Proc:

[code language=”ruby” collapse=”false”]
number_squared = Proc.new { |n| n + n }
[/code]

Modificando nosso método “map!” para aceitar o objeto Proc:

[code language=”ruby” collapse=”false”]
class Array
def map!(proc_object)
self.each_with_index do |value, index|
self[index] = proc_object.call(value)
end
end
end
[/code]

Agora poderíamos fazer o seguinte:

[code language=”ruby” collapse=”false”]
array = [1,2,3,4,5]

array.map!(number_squared)
[/code]

A saída correspondente seria:

[code language=”ruby” collapse=”false”]
=> [1, 4, 6, 8, 10]
[/code]

Observe que não há mais o uso da palavra-chave “yield”. Ao invés de utilizá-la, chamamos diretamente o método “call” do objeto Proc. Dessa forma obtemos o mesmo resultado, mas armazenamos nosso bloco em uma variável para usos posteriores.

Lambdas

Lambdas são quase idênticos aos Procs, mas com duas diferenças chave. Primeiramente, Lambdas checam o número de argumentos recebidos e retorna um “ArgumentError” caso esteja incorreto. Por exemplo:

[code language=”ruby” collapse=”false”]
l = lambda { "Sou um Lambda" }
l.call
=> "Sou um Lambda"
l.call(‘argumento a mais’)
ArgumentError: wrong number of arguments (1 for 0)
[/code]

A segunda diferença é que lambdas usam retornos internos, enquanto o retorno de um Proc é interpretado como um return do método que o chamou. Por exemplo:

[code language=”ruby” collapse=”false”]
def teste_proc
Proc.new { return 1 + 1 }.call
return 2 + 2
end

def teste_lambda
lambda { return 1 + 1 }.call
return 2 + 2
end

teste_proc # => 2
teste_lambda # => 4
[/code]

Como você pôde perceber, o método “teste_proc” retorna “2” porque o retorno do objeto Proc foi interpretado como o retorno do método que o invocou, no caso, foi o retorno do método “teste_proc”.

O método “teste_lambda”, por sua vez, não teve seu retorno substituído pelo de seu lambda interno, por isso o resultado do método foi 4.

Nota

Tanto Proc quanto Lambdas possuem uma sintaxe mais curta a partir do Ruby 1.9.

Para Lambdas, podemos utilizar “->”, como no seguinte exemplo:

[code language=”ruby” collapse=”false”]
curto = ->(a, b) { a + b }
puts curto.call(2, 3)

longo = lambda { |a, b| a + b }
puts longo.call(2, 3)
[/code]

Para Procs, existe um método no Kernel: “Kernel#proc”, que é identico à “Proc.new”. Lembre-se que “proc” é um método, e não uma forma literal como “->” nem uma palavra-chave como “yield”.

[code language=”ruby” collapse=”false”]
curto = proc { |a, b| a + b }
puts curto.call(2, 3)

longo = Proc.new { |a, b| a + b }
puts longo.call(2, 3)
[/code]

Conclusão

Vimos nesse tutorial as diferenças básicas entre Blocks, Procs e Lambdas:

  • Blocks são para uso único.
  • Procs existem como objetos.
  • Lambdas possuem checagem estrita de argumentos.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

*
*
Website