¿Cuándo usar lambda, cuándo usar Proc.new?
Pregunta
En Ruby 1.8, existen diferencias sutiles entre proc/lambda, por un lado, y Proc.new
en el otro.
- ¿Cuáles son esas diferencias?
- ¿Puedes darnos pautas sobre cómo decidir cuál elegir?
- En Ruby 1.9, proc y lambda son diferentes.¿Cual es el trato?
Solución
Otra diferencia importante pero sutil entre los procesos creados con lambda
y procesos creados con Proc.new
así es como manejan el return
declaración:
- en un
lambda
-procesado creado, elreturn
La declaración regresa solo del propio proceso. - en un
Proc.new
-procesado creado, elreturn
La afirmación es un poco más sorprendente:devuelve el control no sólo del proceso, ¡sino también del método que incluye el proceso!
Aquí está lambda
-procesos creados return
en acción.Se comporta de la manera que probablemente esperarías:
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 Proc.new
-procesos creados return
haciendo lo mismo.Estás a punto de ver uno de esos casos en los que Ruby rompe el tan cacareado principio de mínima 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 (además de escribir menos), tiendo a favorecer el uso lambda
encima Proc.new
al realizar procs.
Otros consejos
Para proporcionar más aclaraciones:
Joey dice que el comportamiento de retorno de Proc.new
Es sorprendente.Sin embargo, si consideramos 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.
En realidad, esto explica por qué los Procs son flexibles en cuanto a aridad (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 argumento lambda predeterminado no es una opción en Ruby 1.8, ahora es compatible con Ruby 1.9 con la sintaxis lambda alternativa (como lo indica webmat):
concat = ->(a, b=2){ "#{a}#{b}" }
concat.call(4,5) # => "45"
concat.call(1) # => "12"
Y Michiel de Mare (el OP) se equivoca acerca de que Procs y lambda se comportan igual con arity en Ruby 1.9.He verificado que todavía mantienen el comportamiento de 1.8 como se especifica anteriormente.
break
Las declaraciones en realidad no tienen mucho sentido ni en Procs ni en lambdas.En Procs, la pausa lo devolverá desde Proc.new, que ya se completó.Y no tiene ningún sentido salir de un lambda ya que es esencialmente un método, y nunca saldrías del nivel superior de un método.
next
, redo
, y raise
se comportan igual tanto en Procs como en lambdas.Mientras retry
no está permitido en ninguno de los dos y generará una excepción.
Y finalmente, el proc
El 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 solucionó y devuelve un Proc.Si desea crear un Proc, quédese con Proc.new
.
Para más información, recomiendo ampliamente O'Reilly's. El lenguaje de programación Ruby que es mi fuente para la mayor parte de esta información.
encontré esta página que muestra cuál es la diferencia entre Proc.new
y lambda
son.Según la página, la única diferencia es que una lambda es estricta en cuanto a la cantidad de argumentos que acepta, mientras que Proc.new
convierte los argumentos faltantes a nil
.Aquí hay un ejemplo de sesión IRB que ilustra la diferencia:
irb(main):001:0> l = lambda { |x, y| x + y } => #<Proc:0x00007fc605ec0748@(irb):1> irb(main):002:0> p = Proc.new { |x, y| x + y } => #<Proc:0x00007fc605ea8698@(irb):2> irb(main):003:0> l.call "hello", "world" => "helloworld" irb(main):004:0> p.call "hello", "world" => "helloworld" irb(main):005:0> l.call "hello" ArgumentError: wrong number of arguments (1 for 2) from (irb):1 from (irb):5:in `call' from (irb):5 from :0 irb(main):006:0> p.call "hello" TypeError: can't convert nil into String from (irb):2:in `+' from (irb):2 from (irb):6:in `call' from (irb):6 from :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 una situación promedio.
En cuanto a Ruby 1.9, lo siento, todavía no he investigado 1.9, pero no imagino que lo cambiarían mucho (aunque no confíes en mi palabra, parece que has oído hablar de algunos cambios, así que Probablemente me equivoque en eso).
Proc es más antiguo, pero la semántica de retorno es muy contraintuitiva para mí (al menos cuando estaba aprendiendo el idioma) porque:
- Si está utilizando proc, lo más probable es que esté utilizando algún tipo de paradigma funcional.
- Proc puede regresar fuera del alcance adjunto (ver respuestas anteriores), que es básicamente un goto y de naturaleza altamente no funcional.
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 diferencias sutiles.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.La forma convencional de declarar bloques/lambdas tampoco admitía 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 antigua:
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 desea compilar Ruby1.9 para Leopard o Linux, consulte 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é desea utilizar cada uno.lambda es lo que esperamos que hagan las cosas en un sentido de programación funcional.Es básicamente un método anónimo con el alcance actual vinculado automáticamente.De los dos, lambda es el que probablemente deberías usar.
Proc, por otro lado, es realmente útil para implementar el lenguaje en sí.Por ejemplo, puedes implementar sentencias "if" o bucles "for" con ellas.Cualquier retorno encontrado en el proceso saldrá del método que lo llamó, no solo de la declaración "if".Así es como funcionan los lenguajes, cómo funcionan las declaraciones "si", así que supongo que Ruby usa esto en secreto y simplemente lo expusieron porque parecía poderoso.
Realmente solo necesitarás esto si estás creando nuevas construcciones de lenguaje como bucles, construcciones if-else, etc.
Una buena forma de verlo es que las lambdas se ejecutan en su propio alcance (como si fuera una llamada a un método), mientras que Procs puede verse como ejecutado en línea con el método de llamada, al menos esa es una buena manera de decidir cuál usar. en cada caso.
No noté ningún comentario sobre el tercer método en la pregunta, "proc", que está en desuso, pero se maneja de manera diferente en 1.8 y 1.9.
A continuación se muestra un ejemplo bastante detallado que facilita 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
Cierres en Rubí es una buena descripción general de cómo funcionan los bloques, lambda y proc en Ruby, con Ruby.
lambda funciona como se esperaba, como en otros idiomas.
el cableado Proc.new
Es sorprendente y confuso.
El return
declaración en proceso creado por Proc.new
no sólo devolverá el control de sí mismo, sino que también del método que lo encierra.
def some_method
myproc = Proc.new {return "End."}
myproc.call
# Any code below will not get executed!
# ...
end
Puedes argumentar que Proc.new
inserta código en el método adjunto, al igual que block.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 de ello, 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 profundizar en la respuesta de Accordion Guy:
Darse cuenta de Proc.new
crea un proceso 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
ing desde dentro de un bloque adjunto a una llamada a un método regresará del método, no del bloque, y el Proc.new
El caso es un ejemplo de esto en juego.
(Esto es 1.8.No sé cómo se traduce esto en 1.9.)
Llegué un poco tarde a esto, pero hay una cosa excelente, pero poco conocida, sobre Proc.new
No se menciona en los comentarios en absoluto.Como por documentación:
Proc::new
puede ser llamado sin un bloque sólo dentro de un método con un bloque adjunto, en cuyo caso ese El bloque se convierte enProc
objeto.
Dicho eso, Proc.new
Vamos a encadenar métodos de rendimiento:
def m1
yield 'Finally!' if block_given?
end
def m2
m1 &Proc.new
end
m2 { |e| puts e }
#⇒ Finally!
La diferencia de comportamiento con return
En mi humilde opinión, es la diferencia más importante entre los 2.También prefiero lambda porque se escribe menos que Proc.new :-)
Vale la pena enfatizar que return
en un proc regresa del método léxicamente adjunto, 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 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 en foobar
, fue creado en foo
y así el return
salidas foo
, No solo foobar
.Como escribió Charles Caldwell anteriormente, tiene una sensación de 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.