¿Y cuándo usarías uno en lugar del otro?
¿Y cuándo usarías uno en lugar del otro?
Respuestas:
Una diferencia está en la forma en que manejan los argumentos. Crear un proceso usando proc {}
y Proc.new {}
son equivalentes. Sin embargo, el uso lambda {}
le proporciona un proceso que verifica el número de argumentos que se le pasan. De ri Kernel#lambda
:
Equivalente a Proc.new , excepto que los objetos Proc resultantes verifican el número de parámetros pasados cuando se llama.
Un ejemplo:
p = Proc.new {|a, b| puts a**2+b**2 } # => #<Proc:0x3c7d28@(irb):1>
p.call 1, 2 # => 5
p.call 1 # => NoMethodError: undefined method `**' for nil:NilClass
p.call 1, 2, 3 # => 5
l = lambda {|a, b| puts a**2+b**2 } # => #<Proc:0x15016c@(irb):5 (lambda)>
l.call 1, 2 # => 5
l.call 1 # => ArgumentError: wrong number of arguments (1 for 2)
l.call 1, 2, 3 # => ArgumentError: wrong number of arguments (3 for 2)
Además, como señala Ken, usar return
dentro de un lambda devuelve el valor de ese lambda, pero usarlo return
en un proceso regresa del bloque que lo encierra.
lambda { return :foo }.call # => :foo
return # => LocalJumpError: unexpected return
Proc.new { return :foo }.call # => LocalJumpError: unexpected return
Por lo tanto, para la mayoría de los usos rápidos son los mismos, pero si desea la comprobación automática de argumentos estrictos (que a veces también puede ayudar con la depuración), o si necesita usar la return
declaración para devolver el valor del proceso, use lambda
.
La verdadera diferencia entre procs y lambdas tiene mucho que ver con las palabras clave de control de flujo. Estoy hablando de return
, raise
, break
, redo
, retry
etc. - esas palabras de control. Digamos que tiene una declaración de devolución en un proceso. Cuando llame a su proceso, no solo lo eliminará, sino que también regresará del método de cierre, por ejemplo:
def my_method
puts "before proc"
my_proc = Proc.new do
puts "inside proc"
return
end
my_proc.call
puts "after proc"
end
my_method
shoaib@shoaib-ubuntu-vm:~/tmp$ ruby a.rb
before proc
inside proc
El final puts
en el método nunca se ejecutó, ya que cuando llamamos a nuestro proceso, return
su contenido nos arrojó fuera del método. Sin embargo, si convertimos nuestro proceso a una lambda, obtenemos lo siguiente:
def my_method
puts "before proc"
my_proc = lambda do
puts "inside proc"
return
end
my_proc.call
puts "after proc"
end
my_method
shoaib@shoaib-ubuntu-vm:~/tmp$ ruby a.rb
before proc
inside proc
after proc
El retorno dentro de la lambda solo nos elimina de la propia lambda y el método de cierre continúa ejecutándose. La forma en que las palabras clave de flujo de control se tratan dentro de los procesos y lambdas es la principal diferencia entre ellas.
Solo hay dos diferencias principales.
lambda
verifica el número de argumentos que se le pasan, mientras proc
que a no. Esto significa que a lambda
arrojará un error si le pasa el número incorrecto de argumentos, mientras proc
que a ignorará los argumentos inesperados y los asignará nil
a los que faltan.lambda
regresa, devuelve el control al método de llamada; cuando a proc
regresa, lo hace de inmediato, sin volver al método de llamada.Para ver cómo funciona esto, eche un vistazo al código a continuación. Nuestro primer método llama a proc
; el segundo llama a lambda
.
def batman_ironman_proc
victor = Proc.new { return "Batman will win!" }
victor.call
"Iron Man will win!"
end
puts batman_ironman_proc # prints "Batman will win!"
def batman_ironman_lambda
victor = lambda { return "Batman will win!" }
victor.call
"Iron Man will win!"
end
puts batman_ironman_lambda # prints "Iron Man will win!"
Vea cómo proc
dice "¡Batman ganará!", Esto se debe a que vuelve inmediatamente, sin volver al método batman_ironman_proc.
Nuestro lambda
, sin embargo, vuelve al método después de ser llamado, por lo que el método devuelve el último código que evalúa: "¡Iron Man ganará!"
# Ejemplos de procesos
p = Proc.new { |x| puts x*2 }
[1,2,3].each(&p) # The '&' tells ruby to turn the proc into a block
proc = Proc.new { puts "Hello World" }
proc.call
# Ejemplos Lambda
lam = lambda { |x| puts x*2 }
[1,2,3].each(&lam)
lam = lambda { puts "Hello World" }
lam.call
Diferencias entre Procs y Lambdas
Antes de entrar en las diferencias entre procs y lambdas, es importante mencionar que ambos son objetos Proc.
proc = Proc.new { puts "Hello world" }
lam = lambda { puts "Hello World" }
proc.class # returns 'Proc'
lam.class # returns 'Proc'
Sin embargo, las lambdas son un 'sabor' diferente de los procs. Esta ligera diferencia se muestra al devolver los objetos.
proc # returns '#<Proc:0x007f96b1032d30@(irb):75>'
lam # returns '<Proc:0x007f96b1b41938@(irb):76 (lambda)>'
1. Lambdas verifica el número de argumentos, mientras que los procs no
lam = lambda { |x| puts x } # creates a lambda that takes 1 argument
lam.call(2) # prints out 2
lam.call # ArgumentError: wrong number of arguments (0 for 1)
lam.call(1,2,3) # ArgumentError: wrong number of arguments (3 for 1)
En contraste, a los procesadores no les importa si se les pasa el número incorrecto de argumentos.
proc = Proc.new { |x| puts x } # creates a proc that takes 1 argument
proc.call(2) # prints out 2
proc.call # returns nil
proc.call(1,2,3) # prints out 1 and forgets about the extra arguments
2. Lambdas y procs tratan la palabra clave 'return' de manera diferente
'return' dentro de un lambda activa el código justo fuera del código lambda
def lambda_test
lam = lambda { return }
lam.call
puts "Hello world"
end
lambda_test # calling lambda_test prints 'Hello World'
'return' dentro de un proceso activa el código fuera del método donde se ejecuta el proceso
def proc_test
proc = Proc.new { return }
proc.call
puts "Hello world"
end
proc_test # calling proc_test prints nothing
Y para responder a su otra consulta, ¿cuál usar y cuándo? Seguiré a @jtbandes como él ha mencionado
Por lo tanto, para la mayoría de los usos rápidos son los mismos, pero si desea una verificación automática de argumentos estrictos (que a veces también puede ayudar con la depuración), o si necesita usar la instrucción return para devolver el valor del proceso, use lambda.
Publicado originalmente aquí
En términos generales, las lambdas son más intuitivas que los procesos porque son más similares a los métodos. Son bastante estrictos acerca de arity, y simplemente salen cuando llamas a return. Por esta razón, muchos Rubyists usan lambdas como primera opción, a menos que necesiten las características específicas de los procs.
Procs: Objetos de clase Proc
. Al igual que los bloques, se evalúan en el ámbito donde se definen.
Lambdas también objetos de clase Proc
pero sutilmente diferentes de los procesos normales. Son cierres como bloques y procesos, y como tales se evalúan en el ámbito donde se definen.
Creando Proc
a = Proc.new { |x| x 2 }
Creando lambda
b = lambda { |x| x 2
}
a = proc { |x| x 2 }
es lo mismo quea = Proc.new { |x| x 2 }
Aquí hay otra forma de entender esto.
Un bloque es un fragmento de código adjunto a la invocación a una llamada de un método en un objeto. En el ejemplo a continuación, self es una instancia de una clase anónima que hereda de ActionView :: Base en el marco de Rails (que a su vez incluye muchos módulos auxiliares). La tarjeta es un método que llamamos a uno mismo. Pasamos un argumento al método y luego adjuntamos siempre el bloque al final de la invocación del método:
self.card :contacts do |c|
// a chunk of valid ruby code
end
Ok, entonces estamos pasando una porción de código a un método. Pero, ¿cómo hacemos uso de este bloque? Una opción es convertir el fragmento de código en un objeto. Ruby ofrece tres formas de convertir un fragmento de código en un objeto
# lambda
> l = lambda { |a| a + 1 }
> l.call(1)
=> 2
# Proc.new
> l2= Proc.new { |a| a + 1 }
> l2.call(1)
=> 2
# & as the last method argument with a local variable name
def add(&block)
end
En el método anterior, el & convierte el bloque pasado al método en un objeto y almacena ese objeto en el bloque de variable local. De hecho, podemos mostrar que tiene el mismo comportamiento que lambda y Proc.new:
def add(&block)
block
end
l3 = add { |a| a + 1 }
l3.call(1)
=> 2
Esto es importante. Cuando pasa un bloque a un método y lo convierte usando &, el objeto que crea usa Proc.new para realizar la conversión.
Tenga en cuenta que evité el uso de "proc" como una opción. Eso es porque es Ruby 1.8, es lo mismo que lambda y en Ruby 1.9, es lo mismo que Proc.new y en todas las versiones de Ruby se debe evitar.
Entonces, ¿cuál es la diferencia entre lambda y Proc.new?
Primero, en términos de paso de parámetros, lambda se comporta como una llamada a un método. Planteará una excepción si pasa el número incorrecto de argumentos. Por el contrario, Proc.new se comporta como una asignación paralela. Todos los argumentos no utilizados se convierten en nulo:
> l = lambda {|a,b| puts "#{a} + #{b}" }
=> #<Proc:0x007fbffcb47e40@(irb):19 (lambda)>
> l.call(1)
ArgumentError: wrong number of arguments (1 for 2)
> l2 = Proc.new {|a,b| puts "#{a} + #{b}" }
=> #<Proc:0x007fbffcb261a0@(irb):21>
> l2.call(1)
1 +
En segundo lugar, lambda y Proc.new manejan la palabra clave return de manera diferente. Cuando realiza un retorno dentro de Proc.new, en realidad regresa del método de inclusión, es decir, el contexto circundante. Cuando regresa de un bloque lambda, simplemente regresa del bloque, no del método de cierre. Básicamente, sale de la llamada al bloque y continúa la ejecución con el resto del método de cierre.
> def add(a,b)
l = Proc.new { return a + b}
l.call
puts "now exiting method"
end
> add(1,1)
=> 2 # NOTICE it never prints the message "now exiting method"
> def add(a,b)
l = lambda { return a + b }
l.call
puts "now exiting method"
end
> add(1,1)
=> now exiting method # NOTICE this time it prints the message "now exiting method"
Entonces, ¿por qué esta diferencia de comportamiento? La razón es porque con Proc.new, podemos usar iteradores dentro del contexto de los métodos de cierre y sacar conclusiones lógicas. Mira este ejemplo:
> def print(max)
[1,2,3,4,5].each do |val|
puts val
return if val > max
end
end
> print(3)
1
2
3
4
Esperamos que cuando invoquemos return dentro del iterador, regrese del método de cierre. Recuerde que los bloques pasados a los iteradores se convierten en objetos usando Proc.new y es por eso que cuando usamos return, saldrá del método de cierre.
Puede pensar en lambdas como métodos anónimos, que aíslan bloques de código individuales en un objeto que se puede tratar como un método. En última instancia, piense que una lambda se comporta como un método anónimo y que Proc.new se comporta como un código en línea.
Una publicación útil sobre guías de rubí: bloques, procs y lambdas
Los procesos regresan del método actual, mientras que las lambdas regresan de la propia lambda.
A los procesadores no les importa la cantidad correcta de argumentos, mientras que las lambdas generarán una excepción.
return
declaración devuelve enproc
versuslambda
.