Quando usar lambda, quando usar Proc.new?


336

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

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

3
Veja também: o livro Ruby Programming Language, de Matz e Flanagan, abordou esse tópico de forma abrangente. proc se comporta como uma semântica de rendimento de bloco, enquanto que lambda se comporta como uma semântica de chamada de método - método. Também retornar, quebrar, et. todos se comportam com diff em procs n lambdas #
284


você aceitou a resposta que só diz o que é a diferença entre proc e lambda, enquanto o título da sua pergunta é quando usar essas coisas
Shri

Respostas:


378

Outra diferença importante, mas sutil, entre procs criados com lambdae procs criados com Proc.newé como eles lidam com a returninstrução:

  • Em um lambdaproc criado, a returninstrução retorna apenas do próprio proc
  • Em um Proc.newproc criado, a returninstrução é um pouco mais surpreendente: retorna o controle não apenas do proc, mas também do método que o envolve!

Aqui estão os lambdaprocs criados returnem 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"

Agora, aqui está um Proc.newproc criado por returnfazer a mesma coisa. Você está prestes a ver um daqueles casos em que Ruby quebra o tão elogiado Princípio da 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 este comportamento surpreendente (bem como menos digitação), que tendem a favorecer a utilização de lambdamais Proc.newao fazer procs.


12
Depois, há também o procmétodo É apenas uma abreviação para Proc.new?
panzi


4
@mattdipasquale Nos meus testes, procage como lambdae não como Proc.newem relação às declarações de retorno. Isso significa que o documento ruby ​​é impreciso.
Kelvin

31
@mattdipasquale Desculpe, eu estava meio certo. procage como lambdaem 1.8, mas age como Proc.newem 1.9. Veja a resposta de Peter Wagenet.
Kelvin

55
Por que esse comportamento "surpreendente"? A lambdaé um método anônimo. Como é um método, ele retorna um valor, e o método que o chamou pode fazer com ele o que quiser, incluindo ignorá-lo e retornar um valor diferente. A Procé como colar em um trecho de código. Não funciona como um método. Então, quando um retorno acontece dentro do Proc, isso é apenas parte do código do método que o chamou.
Arcolye

96

Para fornecer mais esclarecimentos:

Joey diz que o comportamento de retorno de Proc.newé surpreendente. No entanto, quando você considera que o Proc.new se comporta como um bloco, isso não é surpreendente, pois é exatamente assim que os blocos se comportam. lambas, por outro lado, se comportam mais como métodos.

Na verdade, isso explica por que os Procs são flexíveis quando se trata de aridade (número de argumentos), enquanto os lambdas não são. Os blocos não exigem que todos os seus argumentos sejam fornecidos, mas os métodos exigem (a menos que um padrão seja fornecido). Embora o fornecimento do argumento lambda padrão não seja uma opção no Ruby 1.8, agora ele é suportado no Ruby 1.9 com a sintaxe lambda alternativa (conforme observado por 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 o lambda se comportando da mesma forma com aridade no Ruby 1.9. Eu verifiquei que eles ainda mantêm o comportamento de 1,8, conforme especificado acima.

breakNa verdade, as instruções não fazem muito sentido em Procs ou lambdas. No Procs, a interrupção retornará você do Proc.new, que já foi concluído. E não faz sentido sair de um lambda, já que é essencialmente um método, e você nunca sairia do nível superior de um método.

next, redoE raisese comportam da mesma em ambos os Procs e lambdas. Considerando que retrynão é permitido em qualquer um e irá gerar uma exceção.

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

Para obter mais informações, recomendo a linguagem de programação Ruby da O'Reilly, que é minha fonte para a maioria dessas informações.


11
"" "No entanto, quando você considera que o Proc.new se comporta como um bloco, isso não é surpreendente, pois é exatamente assim que os blocos se comportam." "" <- block faz parte de um objeto, enquanto o Proc.new cria um objeto. Tanto lambda como Proc.new criam um objeto cuja classe é Proc, por que diff?
weakish

11
A partir do Ruby 2.5, breakde Procs aumenta LocalJumpError, enquanto que breakde lambdas se comporta exatamente como return( ie , return nil).
Masa Sakano

43

Encontrei esta página que mostra qual a diferença entre Proc.newe lambdasão. De acordo com a página, a única diferença é que um lambda é rigoroso quanto ao número de argumentos que aceita, ao passo que Proc.newconverte argumentos ausentes em nil. Aqui está um exemplo de sessão do IRB que ilustra a diferença:

irb (principal): 001: 0> l = lambda {| x, y | x + y}
=> # <Proc: 0x00007fc605ec0748 @ (irb): 1>
irb (principal): 002: 0> p = Proc.novo {| x, y | x + y}
=> # <Proc: 0x00007fc605ea8698 @ (irb): 2>
irb (principal): 003: 0> l.call "olá", "mundo"
=> "helloworld"
irb (main): 004: 0> p.call "olá", "mundo"
=> "helloworld"
irb (principal): 005: 0> l.call "olá"
ArgumentError: número incorreto de argumentos (1 para 2)
    from (irb): 1
    from (irb): 5: em `chamada '
    from (irb): 5
    from: 0
irb (principal): 006: 0> p.call "olá"
TypeError: não é possível converter nada em String
    from (irb): 2: em `+ '
    from (irb): 2
    from (irb): 6: em `chamada '
    from (irb): 6
    from: 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 este sentimento. Usar 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, ainda não pesquisei o 1.9, mas não imagino que eles mudariam muito (não acredite na minha palavra, parece que você já ouviu falar de algumas mudanças, então Provavelmente estou errado lá).


2
procs também retornam diferentemente de lambdas.
Cam

"" "O Proc.new converte argumentos ausentes em zero" "" O Proc.new também ignora argumentos extras (é claro que o lambda reclama isso com um erro).
weakish

16

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

  1. Se você estiver usando proc, provavelmente está usando algum tipo de paradigma funcional.
  2. O processo pode retornar fora do escopo anexo (consulte as respostas anteriores), que é basicamente um goto e de natureza altamente não-funcional.

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


11

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 sintaxe para as lambdas stabby sob 1.9:

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

O Ruby 1.8 não tinha essa sintaxe. A maneira convencional de declarar blocos / lambdas não suportava 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 sintaxe antiga:

l = lambda { |msg = 'inside the regular lambda'|  puts msg }
#=> #<Proc:0x0e5dbc@(irb):1 (lambda)>
l.call
#=> inside the regular lambda
l.call('jeez')
#=> jeez

Se você deseja construir o Ruby1.9 para Leopard ou Linux, confira este artigo (autopromoção sem vergonha).


Parâmetros opcionais dentro da lambda eram muito necessários, fico feliz que eles tenham adicionado em 1.9. Presumo que os blocos também podem ter parâmetros opcionais também (em 1.9)?
mpd

você não está demonstrando parâmetros padrão em blocos, única lambdas
iconoclasta

11

Resposta curta: O que importa é o que returnfaz: lambda retorna de si mesmo, e proc retorna de si mesmo E a função que o chamou.

O que é menos claro é por que você deseja usar cada um. lambda é o que esperamos que as coisas façam no sentido da programação funcional. É 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" loops com elas. Qualquer retorno encontrado no proc retornará do método que o chamou, não apenas a instrução "if". É assim que as linguagens funcionam, como as declarações "if" funcionam, então, meu palpite é que Ruby usa isso debaixo das cobertas e eles apenas o expuseram porque parecia poderoso.

Você realmente precisaria disso apenas se estiver criando novas construções de linguagem, como loops, construções if-else, etc.


11
"lambda retorna de si mesmo, e proc retorna de si mesmo E a função que o chamou" é claramente errada e um mal-entendido muito comum. Um proc é um fechamento e retorna do método que o criou. Veja minha resposta completa em outro lugar da página.
ComDubh

10

Uma boa maneira de ver isso é que lambdas são executadas 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 essa é uma boa maneira de decidir qual usar em cada caso.


8

Não notei nenhum comentário sobre o terceiro método na queston, "proc", que foi descontinuado, mas tratado de maneira 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

11
Matz havia declarado que planejava preteri-lo porque era confuso ter proc e Proc.new retornando resultados diferentes. Em 1.9, eles se comportam da mesma maneira (proc é um apelido para Proc.new). eigenclass.org/hiki/Changes+in+Ruby+1.9#l47
Dave Rapin

@banister: procretornou um lambda em 1,8; agora foi corrigido para retornar um proc em 1.9 - no entanto, esta é uma mudança inusitada; Portanto, não é recomendado o uso mais #
3089

Eu acho que a picareta diz em uma nota de rodapé em algum lugar que proc é efetivamente privado ou algo assim. Não tenho o número exato da página.
precisa saber é o seguinte

7

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


Parei de ler isso depois de ler "uma função não pode aceitar vários blocos - violando o princípio de que fechamentos podem ser passados ​​livremente como valores". Blocos não são fechamentos. Procs são e uma função pode aceitar vários procs.
ComDubh

5

O lambda funciona como esperado, como em outros idiomas.

O fio Proc.newé surpreendente e confuso.

A returninstrução proc criada por Proc.newnão apenas retornará o controle apenas de si mesmo, mas também do método que o inclui .

def some_method
  myproc = Proc.new {return "End."}
  myproc.call

  # Any code below will not get executed!
  # ...
end

Você pode argumentar que Proc.newinsere código no método anexo, assim como o bloco. Mas Proc.newcria um objeto, enquanto o bloco faz parte de um objeto.

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

irb(main):021:0> l = -> (x) { x.to_s }
=> #<Proc:0x8b63750@(irb):21 (lambda)>
irb(main):022:0> p = Proc.new { |x| x.to_s}
=> #<Proc:0x8b59494@(irb):22>
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 `<main>'
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 `<main>'
irb(main):050:0> p.call 1, 2
=> "1"

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


3

Para elaborar a resposta do Acordeão Guy:

Observe que Proc.newcria um processo ao receber um bloco. Eu acredito que lambda {...}é analisado como uma espécie de literal, em vez de uma chamada de método que passa um bloco. returnA entrada de dentro de um bloco anexado a uma chamada de método retornará do método, não do bloco, e o Proc.newcaso é um exemplo disso em jogo.

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


3

Estou um pouco atrasado nisso, mas há uma coisa ótima, mas pouco conhecida, sobre Proc.newnão mencionar nos comentários. Como na documentação :

Proc::newpode ser chamado sem um bloco apenas dentro de um método com um bloco anexado; nesse caso, esse bloco é convertido noProc objeto.

Dito isto, Proc.newvamos encadear métodos de produção:

def m1
  yield 'Finally!' if block_given?
end

def m2
  m1 &Proc.new
end

m2 { |e| puts e } 
#⇒ Finally!

Interessante, ele faz o mesmo que declarar um &blockargumento no def, mas sem ter que fazer isso na lista de argumentos.
Jrochkind

2

Vale ressaltar que returnem um proc retorna do método lexicamente envolvente, ou seja, o método em que o proc foi criado , não o método que chamou o proc. Isso é uma consequência da propriedade de fechamento de procs. Portanto, o código a seguir não gera nada:

def foo
  proc = Proc.new{return}
  foobar(proc)
  puts 'foo'
end

def foobar(proc)
  proc.call
  puts 'foobar'
end

foo

Embora o proc seja executado foobar, ele foi criado fooe, portanto, as returnsaídas foo, não apenas foobar. Como Charles Caldwell escreveu acima, ele tem uma sensação GOTO. Na minha opinião, returné bom em um bloco que é executado em seu contexto lexical, mas é muito menos intuitivo quando usado em um proc que é executado em um contexto diferente.


1

A diferença de comportamento com returnIMHO é a diferença mais importante entre os 2. Eu também prefiro lambda porque é menos digitado que Proc.novo :-)


2
Para atualizar: procs agora pode ser criado usando proc {}. Não tenho certeza de quando isso entrou em vigor, mas é (um pouco) mais fácil do que precisar digitar Proc.new.
aceofbassgreg
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.