Quando usar lambda, quando usar Proc.new?

No Ruby 1.8, existem diferenças sutis entre proc / lambda, por um lado, e Proc.new por outro.

  • Quais são essas diferenças?
  • Você pode dar orientações sobre como decidir qual deles escolher?
  • No Ruby 1.9, proc e lambda são diferentes. Qual é o negócio?

   

Outra diferença importante, mas sutil, entre procs criados com lambda e procs criados com Proc.new é como eles manipulam a declaração de return :

  • Em um proc criado por lambda , a instrução de return retorna apenas do próprio proc
  • Em um Proc.new proc, a declaração de return é um pouco mais surpreendente: ele retorna o controle não apenas do proc, mas também do método que envolve o proc!

Aqui está o return do proc criado por lambda em ação. Ele se comporta de uma maneira que você provavelmente espera:

 def whowouldwin mylambda = lambda {return "Freddy"} mylambda.call # mylambda gets called and returns "Freddy", and execution # continues on the next line return "Jason" end whowouldwin #=> "Jason" 

Ora aqui está o return de um processo Proc.new fazendo a mesma coisa. Você está prestes a ver um desses casos em que Ruby quebra o muito elogiado Princípio de Menor Surpresa:

 def whowouldwin2 myproc = Proc.new {return "Freddy"} myproc.call # myproc gets called and returns "Freddy", # but also returns control from whowhouldwin2! # The line below *never* gets executed. return "Jason" end whowouldwin2 #=> "Freddy" 

Graças a esse comportamento surpreendente (assim como menos digitação), eu costumo favorecer o uso de lambda sobre Proc.new ao fazer procs.

Para fornecer mais esclarecimentos:

Joey diz que o comportamento de retorno do Proc.new é surpreendente. No entanto, quando você considera que Proc.new se comporta como um bloco, isso não é surpreendente, já que é exatamente como os blocos se comportam. Os lambas, por outro lado, comportam-se mais como methods.

Isso realmente explica porque os Procs são flexíveis quando se trata de arity (número de argumentos), enquanto os lambdas não são. Os blocos não exigem que todos os argumentos sejam fornecidos, mas os methods (a menos que um padrão seja fornecido). Embora fornecer o argumento lambda default não seja uma opção no Ruby 1.8, agora é suportado no Ruby 1.9 com a syntax lambda alternativa (conforme observado pelo webmat):

 concat = ->(a, b=2){ "#{a}#{b}" } concat.call(4,5) # => "45" concat.call(1) # => "12" 

E Michiel de Mare (o OP) está incorreto sobre os Procs e lambda se comportando da mesma forma com arity em Ruby 1.9. Verifiquei que eles ainda mantêm o comportamento a partir de 1.8, conforme especificado acima.

Declarações de break não fazem muito sentido em Procs ou lambdas. Em Procs, o intervalo o devolveria do Proc.new que já foi concluído. E não faz sentido romper com um lambda, já que é essencialmente um método, e você nunca romperia com o nível superior de um método.

next , redo e raise se comportam da mesma forma em Procs e lambdas. Considerando que a retry não é permitida em nenhum dos casos e levantará uma exceção.

E, finalmente, o método proc nunca deve ser usado, pois é inconsistente e tem comportamento inesperado. Em Ruby 1.8 ele realmente retorna um lambda! No Ruby 1.9 isso foi corrigido e retorna um Proc. Se você quiser criar um Proc, fique com Proc.new .

Para mais informações, eu recomendo altamente The Ruby Programming Language, de O’Reilly, que é a minha fonte para a maioria dessas informações.

Eu encontrei esta página que mostra qual é a diferença entre Proc.new e lambda . De acordo com a página, a única diferença é que um lambda é estrito quanto ao número de argumentos Proc.new , enquanto Proc.new converte argumentos ausentes em nil . Aqui está um exemplo de session IRB ilustrando a diferença:

  irb (principal): 001: 0> l = lambda {| x, y |  x + y}
 => # 
 irb (principal): 002: 0> p = Proc.new {| x, y |  x + y}
 => # 
 irb (principal): 003: 0> l.call "olá", "mundo"
 => "helloworld"
 irb (principal): 004: 0> p.call "olá", "mundo"
 => "helloworld"
 irb (principal): 005: 0> l.call "olá"
 ArgumentError: número incorreto de argumentos (1 para 2)
     de (irb): 1
     de (irb): 5: em 'chamar'
     de (irb): 5
     a partir de: 0
 irb (principal): 006: 0> p.call "olá"
 TypeError: não é possível converter nil em String
     de (irb): 2: em `+ '
     de (irb): 2
     de (irb): 6: em "chamar"
     de (irb): 6
     a partir de: 0 

A página também recomenda o uso de lambda, a menos que você queira especificamente o comportamento tolerante a erros. Eu concordo com esse sentimento. Usando um lambda parece um pouco mais conciso, e com uma diferença tão insignificante, parece a melhor escolha na situação média.

Quanto ao Ruby 1.9, desculpe, eu não olhei para o 1.9 ainda, mas eu não imagino que eles mudariam tanto assim (não tome minha palavra, parece que você já ouviu falar de algumas mudanças, então Eu provavelmente estou errado lá).

Proc é mais antigo, mas a semântica do retorno é altamente contra-intuitiva para mim (pelo menos quando eu estava aprendendo a língua) porque:

  1. Se você estiver usando proc, provavelmente você está usando algum tipo de paradigma funcional.
  2. Proc pode retornar para fora do escopo de inclusão (veja respostas anteriores), que é basicamente goto e altamente não-funcional por natureza.

Lambda é funcionalmente mais seguro e mais fácil de raciocinar – eu sempre uso em vez de proc.

Eu não posso dizer muito sobre as diferenças sutis. No entanto, posso salientar que o Ruby 1.9 agora permite parâmetros opcionais para lambdas e blocos.

Aqui está a nova syntax para o stabby lambdas em 1.9:

 stabby = ->(msg='inside the stabby lambda') { puts msg } 

O Ruby 1.8 não tinha essa syntax. Nem o modo convencional de declarar blocos / lambdas suporta argumentos opcionais:

 # under 1.8 l = lambda { |msg = 'inside the stabby lambda'| puts msg } SyntaxError: compile error (irb):1: syntax error, unexpected '=', expecting tCOLON2 or '[' or '.' l = lambda { |msg = 'inside the stabby lambda'| puts msg } 

O Ruby 1.9, no entanto, suporta argumentos opcionais, mesmo com a antiga syntax:

 l = lambda { |msg = 'inside the regular lambda'| puts msg } #=> # l.call #=> inside the regular lambda l.call('jeez') #=> jeez 

Se você quer construir o Ruby 1.9 para o Leopard ou Linux, confira este artigo (auto-promoção descarada).

Resposta curta: O que importa é o que o return faz: lambda retorna de si mesmo, e proc retorna de si mesmo E a function que o chamou.

O que é menos claro é por que você quer usar cada um. lambda é o que esperamos que as coisas devam fazer em um sentido de functional programming. É basicamente um método anônimo com o escopo atual vinculado automaticamente. Dos dois, lambda é o que você provavelmente deveria estar usando.

Proc, por outro lado, é realmente útil para implementar a própria linguagem. Por exemplo, você pode implementar instruções “if” ou “for” com elas. Qualquer retorno encontrado no proc retornará do método que o chamou, não apenas da instrução “if”. É assim que as linguagens funcionam, como as instruções “if” funcionam, então meu palpite é que Ruby usa isso embaixo das capas e elas apenas expuseram porque parecia poderoso.

Você só precisaria disso se estivesse criando novas construções de linguagem como loops, construções if-else, etc.

Uma boa maneira de ver isso é que os lambdas são executados em seu próprio escopo (como se fosse uma chamada de método), enquanto Procs pode ser visto como executado em linha com o método de chamada, pelo menos é uma boa maneira de decidir qual deles usar em cada caso.

Eu não notei nenhum comentário sobre o terceiro método no queston, “proc” que está obsoleto, mas tratado de forma diferente em 1.8 e 1.9.

Aqui está um exemplo bastante detalhado que facilita a visualização das diferenças entre as três chamadas semelhantes:

 def meth1 puts "method start" pr = lambda { return } pr.call puts "method end" end def meth2 puts "method start" pr = Proc.new { return } pr.call puts "method end" end def meth3 puts "method start" pr = proc { return } pr.call puts "method end" end puts "Using lambda" meth1 puts "--------" puts "using Proc.new" meth2 puts "--------" puts "using proc" meth3 

Closures em Ruby é uma boa visão geral de como blocos, lambda e proc funcionam em Ruby, com Ruby.

Noções básicas sobre Ruby Blocks, Procs e Lambdas por Robert Sosinski explica claramente esses conceitos de programação e reforça as explicações com código de exemplo. Objetos de methods também são relacionados e abrangidos.

lambda funciona como esperado, como em outros idiomas.

O Proc.new com fio é surpreendente e confuso.

A instrução de return no proc criado por Proc.new não só retornará o controle apenas de si mesmo, mas também do método que o encerra .

 def some_method myproc = Proc.new {return "End."} myproc.call # Any code below will not get executed! # ... end 

Você pode argumentar que o Proc.new insere o código no método de inclusão, assim como o bloco. Mas Proc.new cria um object, enquanto o bloco faz parte de um object.

E há outra diferença entre lambda e Proc.new , que é o tratamento de argumentos (errados). lambda reclama, enquanto Proc.new ignora argumentos extras ou considera a ausência de argumentos como nula.

 irb(main):021:0> l = -> (x) { x.to_s } => # irb(main):022:0> p = Proc.new { |x| x.to_s} => # irb(main):025:0> l.call ArgumentError: wrong number of arguments (0 for 1) from (irb):21:in `block in irb_binding' from (irb):25:in `call' from (irb):25 from /usr/bin/irb:11:in `
' irb(main):026:0> p.call => "" irb(main):049:0> l.call 1, 2 ArgumentError: wrong number of arguments (2 for 1) from (irb):47:in `block in irb_binding' from (irb):49:in `call' from (irb):49 from /usr/bin/irb:11:in `
' irb(main):050:0> p.call 1, 2 => "1"

BTW, proc em Ruby 1.8 cria um lambda, enquanto em Ruby 1.9+ se comporta como Proc.new , o que é realmente confuso.

Para elaborar a resposta do Guy Accordion:

Observe que Proc.new cria um proc out sendo passado por um bloco. Eu acredito que lambda {...} é analisado como uma espécie de literal, ao invés de uma chamada de método que passa por um bloco. return de dentro de um bloco anexado a uma chamada de método retornará do método, não do bloco, e o caso Proc.new é um exemplo disso em jogo.

(Isso é 1.8. Eu não sei como isso se traduz em 1.9.)

Estou um pouco atrasado nisso, mas há uma coisa grande, mas pouco conhecida, sobre o Proc.new não foi mencionado nos comentários. Como por documentação :

Proc::new pode ser chamado sem um bloco somente dentro de um método com um bloco anexado, caso em que esse bloco é convertido para o object Proc .

Dito isso, o Proc.new permite encadear methods de produção:

 def m1 yield 'Finally!' if block_given? end def m2 m1 &Proc.new end m2 { |e| puts e } #⇒ Finally! 

A diferença de comportamento com return é IMHO a diferença mais importante entre os 2. Eu também prefiro lambda porque é menos digitação do que Proc.new 🙂