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

336

En Ruby 1.8, hay diferencias sutiles entre proc / lambda, por un lado, y Proc.newpor 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?
Michiel de Mare
fuente
3
Ver también: el libro Ruby Programming Language de Matz y Flanagan, ha cubierto este tema de manera exhaustiva. proc se comporta como una semántica de rendimiento de bloque, mientras que lambda se comporta como un método - semántica de llamada a método. También regreso, descanso, et. todos se comportan diff en procs n lambdas
Gishu
1
También vea una publicación detallada sobre Control de diferencias de flujo entre Ruby Procs y Lambdas
Akshay Rawat
has aceptado la respuesta que solo dice cuál es la diferencia entre proc y lambda, mientras que el título de tu pregunta es cuándo usar esas cosas
Shri

Respuestas:

378

Otra diferencia importante pero sutil entre los procs creados con lambday los procs creados con Proc.newes cómo manejan la returndeclaración:

  • En un proceso lambdacreado, la returndeclaración solo se devuelve desde el proceso mismo
  • En un proceso Proc.newcreado, la returndeclaració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 lambdacreado returnen 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.newcreado que está returnhaciendo 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 lambdamás Proc.newcuando hago procs.

Joey deVilla
fuente
12
Luego también está el procmétodo. ¿Es solo una abreviatura para Proc.new?
Panzi
66
@panzi, sí, proces equivalente aProc.new
ma11hew28
44
@mattdipasquale En mis pruebas, procactúa como lambday no Proc.newcon respecto a las declaraciones de devolución. Eso significa que el ruby ​​doc es inexacto.
Kelvin
31
@mattdipasquale Lo siento, solo tenía la mitad de razón. procactúa como lambdaen 1.8, pero actúa como Proc.newen 1.9. Ver la respuesta de Peter Wagenet.
Kelvin
55
¿Por qué es este comportamiento "sorprendente"? A lambdaes 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 Proces 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ó.
Arcolye
96

Para proporcionar más aclaraciones:

Joey dice que el comportamiento de retorno de Proc.newes 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.

breaklas 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, redoy se raisecomportan igual en Procs y lambdas. Mientras retryque no está permitido en ninguno de los dos y generará una excepción.

Y finalmente, el procmé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.

Peter Wagenet
fuente
1
"" "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". "<- block es parte de un objeto, mientras que Proc.new crea un objeto. Tanto lambda como Proc.new crean un objeto cuya clase es Proc, ¿por qué diff?
débil
1
A partir de Ruby 2.5, breakProcs aumenta LocalJumpError, mientras que breakdesde lambdas se comporta igual que return( es decir , return nil).
Masa Sakano
43

Encontré esta página que muestra cuál es la diferencia entre Proc.newy 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.newconvierte 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í).

Mike Stone
fuente
2
los procs también regresan de manera diferente a las lambdas.
Cam
"" "Proc.new convierte los argumentos faltantes a cero" "" Proc.new también ignora los argumentos adicionales (por supuesto, lambda se queja de esto con un error).
débil
16

Proc es más antiguo, pero la semántica de retorno es muy contradictoria 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 alcance de cierre (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.

Charles Caldwell
fuente
11

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).

webmat
fuente
Los parámetros opcionales dentro de lambda eran muy necesarios, me alegro de que lo hayan agregado en 1.9. ¿Supongo que los bloques también pueden tener parámetros opcionales también (en 1.9)?
mpd
no está demostrando parámetros predeterminados en bloques, solo lambdas
iconoclasta
11

Respuesta corta: Lo que importa es lo que returnhace: 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.

Evan Moran
fuente
1
"lambda regresa de sí mismo, y proc regresa de sí mismo Y la función que lo llamó" es simplemente erróneo y un malentendido muy común. Un proceso es un cierre y regresa del método que lo creó. Vea mi respuesta completa en otra parte de la página.
ComDubh el
10

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

krusty.ar
fuente
8

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
Dave Rapin
fuente
1
Matz había declarado que planeaba desaprobarlo porque era confuso que proc y Proc.new devolvieran resultados diferentes. Sin embargo, en 1.9 se comportan igual (proc es un alias de Proc.new). eigenclass.org/hiki/Changes+in+Ruby+1.9#l47
Dave Rapin
@banister: procdevolvió 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
Gishu
Creo que el pico dice en una nota al pie de página en algún lugar que el proceso está efectivamente depravado o algo así. No tengo el número de página exacto.
dertoni
7

Closures in Ruby es una buena descripción de cómo funcionan los bloques, lambda y proc en Ruby, con Ruby.

swrobel
fuente
Dejé de leer esto después de leer "una función no puede aceptar múltiples bloques, lo que viola el principio de que los cierres se pueden pasar libremente como valores". Los bloques no son cierres. Los procs son, y una función puede aceptar múltiples procs.
ComDubh
5

lambda funciona como se espera, como en otros idiomas.

El cableado Proc.newes sorprendente y confuso.

La returndeclaración en proc creada por Proc.newno 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.newinserta código en el método de cierre, al igual que el bloque. Pero Proc.newcrea 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.newignora 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, procen Ruby 1.8 crea una lambda, mientras que en Ruby 1.9+ se comporta como Proc.new, lo cual es realmente confuso.

debilitar
fuente
3

Para explicar la respuesta de Accordion Guy:

Observe que Proc.newcrea 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. returnSi 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.)

Peeja
fuente
3

Llego un poco tarde en esto, pero hay una gran cosa, pero poco conocida, que Proc.newno se menciona en los comentarios. Como por documentación :

Proc::newpuede 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.newpermite 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!
Aleksei Matiushkin
fuente
Interesante, hace lo mismo que declarar un &blockargumento en el def, pero sin tener que hacer eso en la lista de argumentos .
jrochkind
2

Vale la pena enfatizar que returnen 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 fooy, por lo tanto, las returnsalidas foo, no solofoobar . Como Charles Caldwell escribió anteriormente, tiene una sensación GOTO. En mi opinión, returnestá 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.

ComDubh
fuente
1

La diferencia en el comportamiento con returnIMHO es la diferencia más importante entre los 2. También prefiero lambda porque es menos tipeado que Proc.new :-)

Orion Edwards
fuente
2
Para actualizar: ahora se pueden crear procs usando proc {}. No estoy seguro de cuándo entró en vigencia, pero es (un poco) más fácil que tener que escribir Proc.new.
aceofbassgreg