Cuándo usar lambda, cuándo usar Proc.¿nuevo?


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. ¿Cuál es el trato?
Author: Andrew Marshall, 2008-08-03

14 answers

Otra diferencia importante pero sutil entre procs creado con lambda y procs creado con Proc.new es cómo manejan la declaración return:

  • En un proc lambda-creado, la instrucción return devuelve solo del proc mismo
  • En un proc Proc.new-creado, la instrucción return es un poco más sorprendente: devuelve el control no solo desde el proc, sino también desde el método que encierra el proc!

Aquí está lambda-creado proc return en acto. 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í hay un Proc.new-creado proc return haciendo lo mismo. Estás a punto de ver uno de esos casos en los que Ruby rompe el tan cacareado 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 menos tipeo), tiendo a preferir usar lambda sobre Proc.new al hacer procs.

 371
Author: Joey deVilla,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-30 06:28:30

Para proporcionar más aclaraciones:

Joey dice que el comportamiento de retorno de Proc.new es sorprendente. Sin embargo, cuando se considera 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 los lambdas no lo son. Los bloques no requieren que se proporcionen todos sus argumentos, pero sí los métodos (a menos que por defecto se proporciona). Si bien proporcionar el argumento lambda default no es una opción en Ruby 1.8, ahora está soportado en Ruby 1.9 con la sintaxis lambda alternativa (como se indica en webmat):

concat = ->(a, b=2){ "#{a}#{b}" }
concat.call(4,5) # => "45"
concat.call(1)   # => "12"

Y Michiel de Mare (el OP) es incorrecto sobre el Procs y lambda comportándose 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, el break te devolvería de Proc.nuevo 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 con el nivel superior de un método.

next, redo, y raise se comportan igual tanto en Procs como en lambdas. Considerando 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 es en realidad devuelve un lambda! En Ruby 1.9 esto ha sido arreglado y devuelve un Proc. Si desea crear un Proc, siga con Proc.new.

Para más información, recomiendo altamente O'Reilly El Lenguaje de Programación Ruby que es mi fuente para la mayor parte de esta información.

 94
Author: Peter Wagenet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-04 05:23:22

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 sobre el número de argumentos que acepta, mientras que Proc.new convierte los argumentos que faltan a nil. Aquí hay un ejemplo de sesión del 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 la situación promedio.

En cuanto a Ruby 1.9, lo siento, todavía no he mirado en 1.9, pero no me imagino que lo cambiarían tanto (no tome mi palabra para ello, sin embargo, parece que ha oído hablar de algunos cambios, así que probablemente estoy equivocado allí).

 41
Author: Mike Stone,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-21 18:36:33

Proc es más antiguo, pero la semántica del retorno es altamente contraintuitiva para mí (al menos cuando estaba aprendiendo el idioma) porque:

  1. Si está utilizando proc, lo más probable es que esté utilizando algún tipo de paradigma funcional.
  2. Proc puede volver fuera del ámbito que lo encierra (ver respuestas anteriores), que es un goto básicamente, 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.

 14
Author: Charles Caldwell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-04-04 18:39:59

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 bajo 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 soporta args 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, soporta 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 quieres construir Ruby1. 9 para Leopard o Linux, echa un vistazo este artículo (shameless self promotion).

 11
Author: webmat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-11-19 21:28:28

Respuesta corta: Lo que importa es lo que hace return: lambda devuelve fuera de sí mismo, y proc devuelve fuera de sí mismo Y la función que lo llamó.

Lo que es menos claro es por qué desea utilizar cada uno. lambda es lo que esperamos que las cosas deben hacer en un sentido de programación funcional. Es básicamente un método anónimo con el ámbito actual enlazado automáticamente. De los dos, lambda es la que probablemente deberías usar.

Proc, por otro lado, es realmente útil para implementar la el lenguaje mismo. Por ejemplo, puede implementar sentencias "if" o bucles " for " con ellas. Cualquier retorno encontrado en el proc regresará del método que lo llamó, no solo de la instrucción "if". Así es como funcionan los lenguajes, cómo funcionan las declaraciones "if", así que mi conjetura es que Ruby usa esto debajo de las cubiertas y simplemente lo expusieron porque parecía poderoso.

Solo necesitará esto si está creando nuevas construcciones de lenguaje como bucles, construcciones if-else, etc.

 11
Author: Evan Moran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-11-11 21:54:05

Una buena manera de verlo es que los lambdas se ejecutan en su propio ámbito (como si fuera una llamada a un método), mientras que los Procs se pueden ver como ejecutados en línea con el método que llama, al menos esa es una buena manera de decidir cuál usar en cada caso.

 9
Author: krusty.ar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-12-09 14:17:51

No noté ningún comentario sobre el tercer método en queston, "proc", que está en desuso, pero se maneja 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
 8
Author: Dave Rapin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-06-25 11:22:32

Closures in Ruby es un buen resumen de cómo funcionan los bloques, lambda y proc en Ruby, con Ruby.

 7
Author: swrobel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-08 06:19:51

Understanding Ruby Blocks, Procs and Lambdas de Robert Sosinski explica claramente estos conceptos de programación y refuerza las explicaciones con código de ejemplo. Los objetos del método están relacionados y cubiertos también.

 6
Author: John,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-03 19:32:14

Lambda funciona como se espera, como en otros idiomas.

El wired Proc.new es sorprendente y confuso.

La instrucción return en proc creada por Proc.new no solo devolverá control solo de sí misma, sino 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

Puede argumentar que Proc.new inserta código en el método de inclusión, al igual que block. Pero Proc.newcrea un objeto, mientras que los bloques son parte de un objeto.

Y hay otra diferencia entre lambda y Proc.new, que es su manejo de argumentos (erróneos). lambda se queja de ello, mientras que Proc.new ignora argumentos adicionales o considera la ausencia de argumentos como nil.

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 que es realmente confuso.

 5
Author: weakish,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-11-21 14:40:45

Para elaborar la respuesta del chico del Acordeón:

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 a un método que pasa un bloque. return ing desde dentro de un bloque unido a una llamada a un método regresará desde el método, no desde el bloque, y el caso Proc.new es un ejemplo de esto en juego.

(Esto es 1.8. No se como esto se traduce a 1.9.)

 3
Author: Peeja,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-07 02:31:40

Estoy un poco atrasado en esto, pero hay una gran pero poco conocida cosa sobre Proc.new que no se menciona en los comentarios en absoluto. Como en la documentación :

Proc::new se puede llamar sin un bloque solo dentro de un método con un bloque adjunto, en cuyo caso ese bloque se convierte en el Proc objeto.

Dicho esto, Proc.new permite encadenar métodos de rendimiento:

def m1
  yield 'Finally!' if block_given?
end

def m2
  m1 &Proc.new
end

m2 { |e| puts e } 
#⇒ Finally!
 2
Author: mudasobwa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-28 13:15:25

La diferencia de comportamiento con return es en MI humilde opinión la diferencia más importante entre el 2. También prefiero lambda porque es menos mecanografía que Proc.nuevo :-)

 1
Author: Orion Edwards,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-08-11 02:09:46