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?
No Ruby 1.8, existem diferenças sutis entre proc / lambda, por um lado, e Proc.newpor outro.
Respostas:
Outra diferença importante, mas sutil, entre procs criados com lambdae procs criados com Proc.newé como eles lidam com a returninstrução:
lambdaproc criado, a returninstrução retorna apenas do próprio procProc.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.
procmétodo É apenas uma abreviação para Proc.new?
procé equivalente aProc.new
procage como lambdae não como Proc.newem relação às declarações de retorno. Isso significa que o documento ruby é impreciso.
procage como lambdaem 1.8, mas age como Proc.newem 1.9. Veja a resposta de Peter Wagenet.
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.
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.
breakde Procs aumenta LocalJumpError, enquanto que breakde lambdas se comporta exatamente como return( ie , return nil).
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á).
Proc é mais antigo, mas a semântica do retorno é altamente contra-intuitiva para mim (pelo menos quando eu estava aprendendo o idioma) porque:
O Lambda é funcionalmente mais seguro e fácil de raciocinar - eu sempre o uso em vez de proc.
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).
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.
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
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 #
Closures em Ruby é uma boa visão geral de como os blocos, lambda e proc funcionam em Ruby, com Ruby.
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.
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.)
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 noProcobjeto.
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!
&blockargumento no def, mas sem ter que fazer isso na lista de argumentos.
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.
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 :-)
proc {}. Não tenho certeza de quando isso entrou em vigor, mas é (um pouco) mais fácil do que precisar digitar Proc.new.