En Ruby 1.8, hay diferencias sutiles entre proc / lambda, por un lado, y Proc.new
por el otro.
- ¿Cuáles son esas diferencias?
- ¿Puedes dar pautas sobre cómo decidir cuál elegir?
- En Ruby 1.9, proc y lambda son diferentes. ¿Cual es el trato?
En Ruby 1.8, hay diferencias sutiles entre proc / lambda, por un lado, y Proc.new
por el otro.
Respuestas:
Otra diferencia importante pero sutil entre los procs creados con lambda
y los procs creados con Proc.new
es cómo manejan la return
declaración:
lambda
creado, la return
declaración solo se devuelve desde el proceso mismoProc.new
creado, la return
declaración es un poco más sorprendente: ¡devuelve el control no solo del proceso, sino también del método que encierra el proceso!Aquí está el proceso lambda
creado return
en acción. Se comporta de una manera que probablemente esperes:
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"
Ahora aquí hay un proceso Proc.new
creado que está return
haciendo lo mismo. Estás a punto de ver uno de esos casos en los que Ruby rompe el tan preciado Principio de la menor sorpresa:
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"
Gracias a este comportamiento sorprendente (así como a menos tipeo), tiendo a usar lambda
más Proc.new
cuando hago procs.
proc
método. ¿Es solo una abreviatura para Proc.new
?
proc
es equivalente aProc.new
proc
actúa como lambda
y no Proc.new
con respecto a las declaraciones de devolución. Eso significa que el ruby doc es inexacto.
proc
actúa como lambda
en 1.8, pero actúa como Proc.new
en 1.9. Ver la respuesta de Peter Wagenet.
lambda
es un método anónimo. Como es un método, devuelve un valor, y el método que lo llamó puede hacer con él lo que quiera, incluido ignorarlo y devolver un valor diferente. A Proc
es como pegar un fragmento de código. No actúa como un método. Entonces, cuando ocurre un retorno dentro de Proc
, eso es solo parte del código del método que lo llamó.
Para proporcionar más aclaraciones:
Joey dice que el comportamiento de retorno de Proc.new
es sorprendente. Sin embargo, si considera que Proc.new se comporta como un bloque, esto no es sorprendente, ya que así es exactamente como se comportan los bloques. Las lambas, por otro lado, se comportan más como métodos.
Esto realmente explica por qué los Procs son flexibles cuando se trata de arity (número de argumentos) mientras que las lambdas no lo son. Los bloques no requieren que se proporcionen todos sus argumentos, pero los métodos sí (a menos que se proporcione un valor predeterminado). Si bien proporcionar el valor predeterminado del argumento lambda no es una opción en Ruby 1.8, ahora se admite en Ruby 1.9 con la sintaxis lambda alternativa (como lo señala webmat):
concat = ->(a, b=2){ "#{a}#{b}" }
concat.call(4,5) # => "45"
concat.call(1) # => "12"
Y Michiel de Mare (el OP) es incorrecto porque los Procs y lambda se comportan de la misma manera con arity en Ruby 1.9. He verificado que aún mantienen el comportamiento de 1.8 como se especifica anteriormente.
break
las declaraciones no tienen mucho sentido ni en Procs ni en lambdas. En Procs, el descanso lo devolvería de Proc.new, que ya se ha completado. Y no tiene ningún sentido romper con una lambda, ya que es esencialmente un método, y nunca romperías desde el nivel superior de un método.
next
, redo
y se raise
comportan igual en Procs y lambdas. Mientras retry
que no está permitido en ninguno de los dos y generará una excepción.
Y finalmente, el proc
método nunca debe usarse, ya que es inconsistente y tiene un comportamiento inesperado. ¡En Ruby 1.8 en realidad devuelve una lambda! En Ruby 1.9 esto se ha solucionado y devuelve un Proc. Si quieres crear un Proc, quédate con Proc.new
.
Para obtener más información, recomiendo el lenguaje de programación The Ruby de O'Reilly, que es mi fuente para la mayoría de esta información.
break
Procs aumenta LocalJumpError
, mientras que break
desde lambdas se comporta igual que return
( es decir , return nil
).
Encontré esta página que muestra cuál es la diferencia entre Proc.new
y lambda
. Según la página, la única diferencia es que una lambda es estricta con respecto al número de argumentos que acepta, mientras que Proc.new
convierte los argumentos faltantes en nil
. Aquí hay un ejemplo de sesión IRB que ilustra la diferencia:
irb (principal): 001: 0> l = lambda {| x, y | x + y} => # <Proceso: 0x00007fc605ec0748 @ (irb): 1> irb (main): 002: 0> p = Proc.new {| x, y | x + y} => # <Proceso: 0x00007fc605ea8698 @ (irb): 2> irb (principal): 003: 0> l.llamar "hola", "mundo" => "helloworld" irb (main): 004: 0> p.call "hola", "mundo" => "helloworld" irb (principal): 005: 0> l.llamar "hola" ArgumentError: número incorrecto de argumentos (1 para 2) de (irb): 1 from (irb): 5: en `call ' desde (irb): 5 desde: 0 irb (main): 006: 0> p.call "hola" TypeError: no se puede convertir nil en String from (irb): 2: en '+' desde (irb): 2 from (irb): 6: en `call ' desde (irb): 6 desde: 0
La página también recomienda usar lambda a menos que desee específicamente el comportamiento tolerante a errores. Estoy de acuerdo con este sentimiento. Usar una lambda parece un poco más conciso, y con una diferencia tan insignificante, parece la mejor opción en la situación promedio.
En cuanto a Ruby 1.9, lo siento, aún no he investigado 1.9, pero no creo que lo cambien tanto (no creas mi palabra, parece que has oído hablar de algunos cambios, así que Probablemente estoy equivocado allí).
Proc es más antiguo, pero la semántica de retorno es muy contradictoria para mí (al menos cuando estaba aprendiendo el idioma) porque:
Lambda es funcionalmente más seguro y más fácil de razonar: siempre lo uso en lugar de proc.
No puedo decir mucho sobre las sutiles diferencias. Sin embargo, puedo señalar que Ruby 1.9 ahora permite parámetros opcionales para lambdas y bloques.
Aquí está la nueva sintaxis para las lambdas stabby en 1.9:
stabby = ->(msg='inside the stabby lambda') { puts msg }
Ruby 1.8 no tenía esa sintaxis. Tampoco la forma convencional de declarar bloques / lambdas admite argumentos opcionales:
# 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 }
Ruby 1.9, sin embargo, admite argumentos opcionales incluso con la sintaxis anterior:
l = lambda { |msg = 'inside the regular lambda'| puts msg }
#=> #<Proc:0x0e5dbc@(irb):1 (lambda)>
l.call
#=> inside the regular lambda
l.call('jeez')
#=> jeez
Si quieres construir Ruby1.9 para Leopard o Linux, mira este artículo (autopromoción descarada).
Respuesta corta: Lo que importa es lo que return
hace: lambda regresa de sí mismo, y proc regresa de sí mismo Y de la función que lo llamó.
Lo que está menos claro es por qué quieres usar cada uno. lambda es lo que esperamos que las cosas hagan en un sentido de programación funcional. Básicamente es un método anónimo con el alcance actual automáticamente vinculado. De los dos, lambda es el que probablemente deberías estar usando.
Proc, por otro lado, es realmente útil para implementar el lenguaje en sí. Por ejemplo, puede implementar sentencias "if" o bucles "for" con ellas. Cualquier retorno que se encuentre en el proceso volverá del método que lo llamó, no solo la declaración "if". Así es como funcionan los idiomas, cómo funcionan las declaraciones "si", así que supongo que Ruby usa esto debajo de las cubiertas y simplemente lo expusieron porque parecía poderoso.
Realmente solo necesitaría esto si está creando nuevas construcciones de lenguaje como bucles, construcciones if-else, etc.
No noté ningún comentario sobre el tercer método en el queston, "proc", que está en desuso, pero se manejó de manera diferente en 1.8 y 1.9.
Aquí hay un ejemplo bastante detallado que hace que sea fácil ver las diferencias entre las tres llamadas similares:
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
devolvió una lambda en 1.8; ahora se ha solucionado para devolver un proceso en 1.9, sin embargo, este es un cambio radical; por lo tanto, no se recomienda usar más
Closures in Ruby es una buena descripción de cómo funcionan los bloques, lambda y proc en Ruby, con Ruby.
lambda funciona como se espera, como en otros idiomas.
El cableado Proc.new
es sorprendente y confuso.
La return
declaración en proc creada por Proc.new
no solo devolverá el control solo de sí misma, sino también del método que la encierra .
def some_method
myproc = Proc.new {return "End."}
myproc.call
# Any code below will not get executed!
# ...
end
Puede argumentar que Proc.new
inserta código en el método de cierre, al igual que el bloque. Pero Proc.new
crea un objeto, mientras que el bloque es parte de un objeto.
Y hay otra diferencia entre lambda y Proc.new
, que es su manejo de argumentos (incorrectos). lambda se queja al respecto, mientras Proc.new
ignora argumentos adicionales o considera la ausencia de argumentos como nula.
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"
Por cierto, proc
en Ruby 1.8 crea una lambda, mientras que en Ruby 1.9+ se comporta como Proc.new
, lo cual es realmente confuso.
Para explicar la respuesta de Accordion Guy:
Observe que Proc.new
crea un proceso de salida al pasarle un bloque. Creo que lambda {...}
se analiza como una especie de literal, en lugar de una llamada a un método que pasa un bloque. return
Si se ingresa desde un bloque adjunto a una llamada al método, se devolverá desde el método, no desde el bloque, yProc.new
caso es un ejemplo de esto en juego.
(Esto es 1.8. No sé cómo se traduce esto a 1.9.)
Llego un poco tarde en esto, pero hay una gran cosa, pero poco conocida, que Proc.new
no se menciona en los comentarios. Como por documentación :
Proc::new
puede llamarse sin un bloque solo dentro de un método con un bloque adjunto, en cuyo caso ese bloque se convierte en elProc
objeto.
Dicho esto, Proc.new
permite encadenar los métodos de rendimiento:
def m1
yield 'Finally!' if block_given?
end
def m2
m1 &Proc.new
end
m2 { |e| puts e }
#⇒ Finally!
&block
argumento en el def
, pero sin tener que hacer eso en la lista de argumentos .
Vale la pena enfatizar que return
en un proceso vuelve del método que encierra léxicamente, es decir, el método donde se creó el proceso , no el método que llamó al proceso. Esto es una consecuencia de la propiedad de cierre de los procs. Entonces el siguiente código no genera nada:
def foo
proc = Proc.new{return}
foobar(proc)
puts 'foo'
end
def foobar(proc)
proc.call
puts 'foobar'
end
foo
Aunque el proceso se ejecuta foobar
, se creó en foo
y, por lo tanto, las return
salidas foo
, no solofoobar
. Como Charles Caldwell escribió anteriormente, tiene una sensación GOTO. En mi opinión, return
está bien en un bloque que se ejecuta en su contexto léxico, pero es mucho menos intuitivo cuando se usa en un proceso que se ejecuta en un contexto diferente.
La diferencia en el comportamiento con return
IMHO es la diferencia más importante entre los 2. También prefiero lambda porque es menos tipeado que Proc.new :-)
proc {}
. No estoy seguro de cuándo entró en vigencia, pero es (un poco) más fácil que tener que escribir Proc.new.