lambda ruby
Cuándo usar lambda, cuándo usar Proc.nuevo? (14)
En Ruby 1.8, hay diferencias sutiles entre proc / lambda por un lado, y
Proc.new
por el otro.
- ¿Cuáles son esas diferencias?
- ¿Puede dar pautas sobre cómo decidir cuál elegir?
- En Ruby 1.9, proc y lambda son diferentes. ¿Cual es el trato?
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 un lambda es estricto respecto al número de argumentos que acepta, mientras que
Proc.new
convierte los argumentos que faltan en
nil
.
Aquí hay un ejemplo de sesión de 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 ser la mejor opción en la situación promedio.
En cuanto a Ruby 1.9, lo siento, aún no he visto la versión 1.9, pero no creo que lo cambien demasiado (aunque no creas en mi palabra, parece que has oído hablar de algunos cambios, así que Probablemente me equivoque allí.
Estoy un poco atrasado en esto, pero hay una gran cosa poco conocida sobre
Proc.new
no se menciona en los comentarios.
Como por la
documentation
:
Se puede llamar a
Proc::new
sin un bloque solo dentro de un método con un bloque adjunto, en cuyo caso ese bloque se convierte en el objetoProc
.
Dicho esto,
Proc.new
permite encadenar métodos de producción:
def m1
yield ''Finally!'' if block_given?
end
def m2
m1 &Proc.new
end
m2 { |e| puts e }
#⇒ Finally!
La diferencia en el comportamiento con el
return
es, en mi opinión, la diferencia más importante entre los 2. También prefiero lambda porque es menos tipográfica que Proc.new :-)
Lambda funciona como se espera, como en otros idiomas.
El
Proc.new
cableado es sorprendente y confuso.
La declaración de
return
en proc creada por
Proc.new
no solo devolverá el control solo desde sí mismo, sino
también desde el método que lo contiene
.
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
Proc.new
, 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 los argumentos (incorrectos).
lambda se queja de ello, mientras que
Proc.new
ignora los argumentos adicionales o considera que la ausencia de argumentos es 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 un lambda, mientras que en Ruby 1.9+ se comporta como
Proc.new
, lo que es realmente confuso.
No noté ningún comentario sobre el tercer método en la búsqueda, "proc" que está en desuso, pero se maneja de manera diferente en 1.8 y 1.9.
Aquí hay 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
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 los lambdas stabby debajo de 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 era compatible con 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 compilar Ruby1.9 para Leopard o Linux, consulta este artículo (autopromoción descarada).
Otra diferencia importante pero sutil entre procs creados con
lambda
y procs creados con
Proc.new
es cómo manejan la declaración de
return
:
-
En un proc creado con
lambda
, la declaración dereturn
devuelve solo desde el propio proc. -
En un
Proc.new
proc, la declaración dereturn
es un poco más sorprendente: ¡devuelve el control no solo del proceso, sino también del método que incluye el proceso!
Aquí está el
return
de proc creado en
lambda
en acción.
Se comporta de una manera que probablemente esperas:
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í está el
Proc.new
un
Proc.new
proc haciendo lo mismo.
Estás a punto de ver uno de esos casos en los que Ruby rompe el tan aclamado 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 sorprendente comportamiento (y menos escritura), tiendo a preferir el uso de
lambda
en lugar de
Proc.new
cuando se realizan procs.
Para profundizar en la respuesta de Accordion Guy:
Observe que
Proc.new
crea un proc out al pasar un bloque.
Creo que
lambda {...}
se analiza como una especie de literal, en lugar de una llamada de método que pasa un bloque.
return
desde dentro de un bloque adjunto a una llamada de método regresará del método, no del bloque, y el caso
Proc.new
es un ejemplo de esto en juego.
(Esto es 1.8. No sé cómo se traduce a 1.9.)
Para proporcionar más aclaraciones:
Joey dice que el comportamiento de retorno de
Proc.new
es sorprendente.
Sin embargo, cuando consideras que Proc.new se comporta como un bloque, esto no es sorprendente, ya que es exactamente cómo 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 sí los métodos (a menos que se proporcione un valor predeterminado). Mientras que proporcionar el argumento lambda por defecto no es una opción en Ruby 1.8, ahora se admite en Ruby 1.9 con la sintaxis lambda alternativa (como lo indica el webmat):
concat = ->(a, b=2){ "#{a}#{b}" }
concat.call(4,5) # => "45"
concat.call(1) # => "12"
Y Michiel de Mare (el OP) es incorrecto acerca de que Procs y lambda se comportan igual con Arity en Ruby 1.9. He verificado que aún mantienen el comportamiento desde 1.8 como se especifica anteriormente.
break
declaraciones de
break
realidad no tienen mucho sentido, ya sea en Procs o en lambdas.
En Procs, el descanso lo devolvería de Proc.nuevo que ya se ha completado.
Y no tiene ningún sentido romper con un lambda ya que es esencialmente un método, y nunca se rompería desde el nivel superior de un método.
next
,
redo
y
raise
comportan de la misma manera tanto en Procs como en lambdas.
Mientras que
retry
no está permitido en ninguno de los dos y generará una excepción.
Y, finalmente, el método
proc
nunca debe usarse, ya que es inconsistente y tiene un comportamiento inesperado.
En Ruby 1.8 en realidad devuelve un lambda!
En Ruby 1.9 esto ha sido arreglado y devuelve un Proc.
Si quieres crear un Proc, quédate con
Proc.new
.
Para más información, recomiendo encarecidamente el lenguaje de programación de Ruby de O''Reilly, que es mi fuente para la mayor parte de esta información.
Proc es más viejo, pero la semántica del retorno es muy contradictoria para mí (al menos cuando estaba aprendiendo el idioma) porque:
- Si está utilizando proc, lo más probable es que esté usando algún tipo de paradigma funcional.
- Proc puede regresar al ámbito de aplicación (ver las 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.
Respuesta corta: lo que importa es lo que hace el
return
: lambda retorna fuera de sí mismo, y proc vuelve fuera de sí mismo Y 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 deberían hacer en un sentido de programación funcional. Es básicamente un método anónimo con el alcance actual automáticamente vinculado. De los dos, lambda es la que probablemente deberías usar.
Proc, por otro lado, es realmente útil para implementar el lenguaje en sí. Por ejemplo, puede implementar sentencias "if" o bucles "for" con ellos. Cualquier devolución encontrada en el proceso se devolverá fuera del método que la llamó, no solo la declaración "if". Así es como funcionan los lenguajes, cómo funcionan las afirmaciones "si", así que supongo que Ruby usa esto debajo de las portadas y lo expusieron porque parecía poderoso.
Solo necesitaría esto realmente si está creando nuevas construcciones de lenguaje como bucles, construcciones if-else, etc.
Una buena manera de verlo es que las lambdas se ejecutan en su propio alcance (como si fuera una llamada de método), mientras que Procs puede verse como ejecutado en línea con el método de llamada, al menos esa es una buena forma de decidir cuál usar. en cada caso.
Vale la pena enfatizar que el
return
en un proceso se realiza desde el método de encierro léxico, es decir,
el método en el que se creó el proceso
,
no
el método que llamó al proceso.
Esta es una consecuencia de la propiedad de cierre de procs.
Entonces el siguiente código no produce 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
, se creó en
foo
y, por lo tanto, el
return
sale de
foo
, no solo de
foobar
.
Como Charles Caldwell escribió anteriormente, tiene una sensación de GOTO.
En mi opinión, el
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.
Closures in Ruby es un buen resumen de cómo funcionan los bloques, lambda y proc en Ruby, con Ruby.