Conceitos na prática: Ruby Procs

Nos posts anteriores, foi mostrado um pouco sobre lambdas e blocos em Ruby. No entanto existe mais uma outra estrutura que possui funcionalidade bem similar, mas com diferenças sutis e importantes. Nesse post vou falar um pouco sobre Proc e as as suas diferenças.

O que são Procs?

Se você já leu os posts anteriores sobre lambdas e blocos, o conceito de ter um pedaço de código que pode ser executado e passado como parâmetro para outros métodos não deve ser novidade. Bom, isso é o que um Proc é.

Veja no exemplo a seguir como é a sintaxe para criação de procs:

hello_proc = Proc.new do
  puts "Hello World"
end

hello_proc.call

Ou seja, é bem semelhante a lambdas. E para ficar mais parecido ainda, podemos usar apenas “proc” ao invés de “Proc.new”:

hello_proc = proc do
  puts "Hello World"
end

hello_proc.call

Além da sintaxe, as propriedades de blocos e lambdas são bem semelhantes também, já que um lambda é uma versão especial de um bloco.

hello_lambda = lambda {}
hello_proc = proc {}

hello_proc.class #=> Proc
hello_lambda.class #=> Proc

Então todos aqueles exemplos que vimos no post anterior sobre conversões entre blocos e lambdas também se aplica a Procs. Dito isto, vou tentar focar mais nas diferenças entre blocos, lambdas e procs [1].

Blocos vs. Procs/Lambdas

A primeira grande diferença é que apenas um bloco pode ser passado para um método.

Nos exemplos do post sobre blocos nós vimos que não é necessário explicitar que o bloco será passado, a menos que você precise referenciá-lo dentro do método (para passar para outro método, por exemplo). No entanto não é possível passar mais de um bloco para um mesmo método. Precisamos usar lambdas ou procs para alcanças este objetivo.

Uma outra diferença é a sintaxe. Blocos não podem ser executados chamando o método “call”, mas sim utilizando a palavra chave yield. Tudo bem, essa não é uma grande diferença.

Procs vs. Lambdas

Agora as diferenças começam a ficar interessantes!

Vamos tentar utilizar alguns exemplos, observe os dois pedaços de código abaixo, um usa lambda e outro usa proc:

hello_proc = proc do |msg|
  puts msg
end

hello_proc.call
hello_lambda = lambda do |msg|
  puts msg
end

hello_lambda.call

Qual deve ser o resultado de cada pedaço de código? O mesmo? Note que agora o proc e o lambda estão recebendo um argumento que será exibido no terminal.

Se você tentar executar o primeiro código (com proc) verá que a saída é uma linha vazia, pois o parâmetro “msg” não foi passado. Assim, podemos ver que procs automaticamente atribuem “nil” para variáves cujo valor não tenha sido definido.

Já o segundo código (com lambda) lançará uma exceção “ArgumentError: wrong number of arguments (0 for 1)”! Ou seja, os argumentos são necessários para a execução de um lambda. No entanto, podemos contornar esse problema utilizando valores default para o lambda, como mostra o exemplo a seguir

hello_lambda = lambda do |msg = nil|
  puts msg
end

hello_lambda.call

Outra grande diferença está em como o “return” é tratado. O “return” em um lambda retorna o fluxo de execução para o método onde o lambda foi chamado:

hello_lambda = lambda do |msg|
  return msg
end

def run_hello_block(block)
  puts "Preparando para executar lambda..."
  puts "Lambda em execução: #{block.call("Hello World")}"
  return "Lambda executado com sucesso!"
end

puts run_hello_block(hello_lambda)

Nesse caso, tudo funciona como esperado e todas as mensagens são exibidas. No entanto, o mesmo pedaço de código, agora utilizando proc, tem um resultado bem diferente:

hello_proc = proc do |msg|
  return msg
end

def run_hello_block(block)
  puts "Preparando para executar proc..."
  puts "Proc em execução: #{block.call("Hello World")}"
  return "Proc executado com sucesso!"
end

puts run_hello_block(hello_proc)

Ao tentar executar este código veremos uma exceção “LocalJumpError: unexpected return”. Esta exceção é lançada quando tentamos executar um return dentro de um argumento [2], exceto quando o argumento é um lambda.

Uma maneira de resolver o problema seria definir o proc dentro do método, assim ele não seria mais um parâmetro e poderia retornar algo:

def run_hello_block
  hello_proc = proc do |msg|
    return msg
  end

  puts "Preparando para executar proc..."
  puts "Proc em execução: #{hello_proc.call("Hello World")}"
  return "Proc executado com sucesso!"
end

puts run_hello_block

Com o código acima podemos ver que é possível utilizar o return dentro de um proc. O problema é como o escopo é gerenciado. O proc foi criado fora do escopo do método “run_hello_block”, assim quando ele tenta retornar, e o escopo mudou, a exceção é lançada.

Outra solução bem simples é não utilizar a palavra-chave “return”, já que em ruby a última linha sempre é retornada:

hello_proc = proc do |msg|
  msg
end

def run_hello_block(block)
  puts "Preparando para executar proc..."
  puts "Proc em execução: #{block.call("Hello World")}"
  return "Proc executado com sucesso!"
end

puts run_hello_block(hello_proc)

Existem várias outras diferenças entre Blocos, Procs e Lambdas que não vou falar aqui, mas essas são as principais. Espero que os exemplos possam lhe ajudar a entender melhor essas diferenças.

Referências

[1] Ahsan Sharafuddin: Closures in Ruby, http://technicalecstasy.net/closures-in-ruby-blocks-procs-and-lambdas/

[2] Robert Sosinski: Understanding Ruby Blocks, Procs and Lambdas http://www.robertsosinski.com/2008/12/21/understanding-ruby-blocks-procs-and-lambdas/

Anúncios

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s