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 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.new
por outro.
Respostas:
Outra diferença importante, mas sutil, entre procs criados com lambda
e procs criados com Proc.new
é como eles lidam com a return
instrução:
lambda
proc criado, a return
instrução retorna apenas do próprio procProc.new
proc criado, a return
instruçã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 lambda
procs criados return
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"
Agora, aqui está um Proc.new
proc criado por return
fazer 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 lambda
mais Proc.new
ao fazer procs.
proc
método É apenas uma abreviação para Proc.new
?
proc
é equivalente aProc.new
proc
age como lambda
e não como Proc.new
em relação às declarações de retorno. Isso significa que o documento ruby é impreciso.
proc
age como lambda
em 1.8, mas age como Proc.new
em 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.
break
Na 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
, redo
E raise
se comportam da mesma em ambos os Procs e lambdas. Considerando que retry
não é permitido em qualquer um e irá gerar uma exceção.
E, finalmente, o proc
mé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.
break
de Procs aumenta LocalJumpError
, enquanto que break
de lambdas se comporta exatamente como return
( ie , return nil
).
Encontrei esta página que mostra qual a diferença entre Proc.new
e lambda
sã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.new
converte 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 return
faz: 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
proc
retornou 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 return
instrução proc criada por Proc.new
nã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.new
insere código no método anexo, assim como o bloco. Mas Proc.new
cria 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.new
ignora 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, proc
no 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.new
cria 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. return
A entrada de dentro de um bloco anexado a uma chamada de método retornará do método, não do bloco, e o Proc.new
caso é 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.new
não mencionar nos comentários. Como na documentação :
Proc::new
pode 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.new
vamos 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!
&block
argumento no def
, mas sem ter que fazer isso na lista de argumentos.
Vale ressaltar que return
em 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 foo
e, portanto, as return
saí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 return
IMHO é 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.