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.