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?
¿Fue útil?

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, el return La declaración regresa solo del propio proceso.
  • en un Proc.new-procesado creado, el return 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:

  1. Si está utilizando proc, lo más probable es que esté utilizando algún tipo de paradigma funcional.
  2. 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. returning 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 en Proc 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.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top