ruby - ¿Dónde y cuándo usar Lambda?
language-agnostic (7)
Estoy tratando de entender por qué realmente necesitamos lambda o proc en ruby (o en cualquier otro idioma).
#method
def add a,b
c = a+b
end
#using proc
def add_proc a,b
f = Proc.new {|x,y| x + y }
f.call a,b
end
#using lambda function
def add_lambda a,b
f = lambda {|x,y| x + y}
f.call a,b
end
puts add 1,1
puts add_proc 1,2
puts add_lambda 1,3
Puedo hacer una adición simple usando: 1. función normal def, 2. usando proc y 3. usando lambda.
¿Pero por qué y dónde usar lambda en el mundo real? Cualquier ejemplo donde las funciones no puedan ser usadas y lambda debería ser usado.
Los bloques son más o menos lo mismo
Bueno, en Ruby, uno no suele usar lambda o proc, porque los bloques son más o menos lo mismo y mucho más conveniente.
Los usos son infinitos, pero podemos enumerar algunos casos típicos. Uno normalmente piensa en las funciones como bloques de nivel inferior que realizan una parte del proceso, tal vez escritos en general y convertidos en una biblioteca.
Pero a menudo uno quiere automatizar el contenedor y proporcionar una biblioteca personalizada. Imagine una función que hace una conexión HTTP o HTTPS, o una conexión TCP directa, alimenta la E / S a su cliente y luego cierra la conexión. O tal vez simplemente haga lo mismo con un viejo archivo simple.
Entonces, en Ruby pondríamos la función en una biblioteca y le llevaremos un bloque para que el usuario ... el cliente ... el "que llama" escriba su lógica de aplicación.
En otro idioma, esto debería hacerse con una clase que implemente una interfaz o un puntero de función. Ruby tiene bloques, pero todos son ejemplos de un patrón de diseño de estilo lambda.
Encontré esto útil para entender las diferencias:
http://www.robertsosinski.com/2008/12/21/understanding-ruby-blocks-procs-and-lambdas/
Pero, en general, el punto es que a veces escribes un método, pero no sabes lo que vas a querer hacer en un determinado punto de ese método, por lo que dejas que la persona que llama lo decida.
P.ej:
def iterate_over_two_arrays(arr1, arr2, the_proc)
arr1.each do |x|
arr2.each do |y|
# ok I''m iterating over two arrays, but I could do lots of useful things now
# so I''ll leave it up to the caller to decide by passing in a proc
the_proc.call(x,y)
end
end
end
Luego, en lugar de escribir un método iterate_over_two_arrays_and_print_sum
y un método iterate_over_two_arrays_and_print_product
, simplemente llame:
iterate_over_two_arrays([1,2,3], [4,5,6], Proc.new {|x,y| puts x + y }
o
iterate_over_two_arrays([1,2,3], [4,5,6], Proc.new {|x,y| puts x * y }
entonces es mas flexible
Es cierto, no necesitas funciones anónimas (o lambdas, o como quieras llamarlas). Pero hay muchas cosas que no necesitas . No necesita clases: simplemente transfiera todas las variables de instancia a las funciones normales. Entonces
class Foo
attr_accessor :bar, :baz
def frob(x)
bar = baz*x
end
end
se convertiría
def new_Foo(bar,baz)
[bar,baz]
end
def bar(foo)
foo[0]
end
# Other attribute accessors stripped for brevity''s sake
def frob(foo,x)
foo[0] = foo[1]*x
end
De manera similar, no necesita ningún bucle excepto el loop...end
con if
y break
. Podría seguir y seguir. 1 Pero desea programar con clases en Ruby. Desea poder utilizar while
loops, o incluso array.each { |x| ... }
array.each { |x| ... }
, y desea poder utilizar a unless
lugar de if not
.
Al igual que estas características, las funciones anónimas están ahí para ayudarlo a expresar las cosas de manera elegante, concisa y sensata. Poder escribir some_function(lambda { |x,y| x + f(y) })
es mucho mejor que tener que escribir
def temp(x,y)
x + f(y)
end
some_function temp
Es mucho más voluminoso tener que interrumpir el flujo de código para escribir una función de alimentación def
, que luego debe recibir un nombre inútil, cuando es tan claro escribir la operación en línea. Es cierto que no debes usar lambda en ninguna parte, pero hay muchos lugares en los que prefiero usar una lambda.
Ruby resuelve muchos de los casos de uso de lambda con bloques: todas las funciones como each
, map
y open
que pueden tomar un bloque como argumento básicamente toman una función anónima especial. array.map { |x| f(x) + g(x) }
array.map { |x| f(x) + g(x) }
es lo mismo que array.map(&lambda { |x| f(x) + g(x) })
(donde &
hace que la lambda sea "especial" de la misma manera que el bloque desnudo es). De nuevo, podría escribir una función de alimentación por separado cada vez, pero ¿por qué querría hacerlo?
Los lenguajes distintos de Ruby que admiten ese estilo de programación no tienen bloques, pero a menudo admiten una sintaxis lambda de peso ligero, como Haskell''s /x -> fx + gx
, o C # ''s x => f(x) + g(x);
2 . Cada vez que tengo una función que necesita tomar un comportamiento abstracto, como map
, o each
, o on_clicked
, voy a estar agradecido por la capacidad de pasar una lambda en lugar de una función con nombre, porque es mucho más fácil . Finalmente, dejas de pensar en ellos como algo especial: son tan emocionantes como la sintaxis literal para las matrices en lugar de empty().append(1).append(2).append(3)
. Solo otra parte útil del lenguaje.
1: en el caso degenerado, solo necesita ocho instrucciones : +-<>[].,
. <>
mueve un "puntero" imaginario a lo largo de una matriz; +-
incrementa y disminuye el entero en la celda actual; []
realizar un ciclo while non-zero; y .,
hacer entrada y salida. De hecho, solo necesita una sola instrucción , como subleq abc
(restar a
de b
y saltar a c
si el resultado es menor o igual a cero).
2: nunca he usado C #, así que si esa sintaxis es incorrecta, puedes corregirla.
Se usan como funciones de "orden superior". Básicamente, para los casos en los que pasa una función a otra, de modo que la función receptora pueda llamar a la función transferida de acuerdo con su propia lógica.
Esto es común en Ruby para la iteración, por ejemplo, some_list.each {| item | ...} para hacer algo con each
item
de some_list
. Aunque tenga en cuenta que no usamos la palabra clave lambda
; como se señaló, un bloqueo es básicamente lo mismo.
En Python (ya que tenemos una etiqueta language-agnostic
del idioma en esta pregunta) no se puede escribir nada como un bloque de Ruby, por lo que la palabra clave lambda
aparece con más frecuencia. Sin embargo, puede obtener un efecto de "acceso directo" similar a partir de las comprensiones de la lista y las expresiones del generador.
1) Es solo una conveniencia. No necesita nombrar ciertos bloques
special_sort(array, :compare_proc => lambda { |left, right| left.special_param <=> right.special_param }
(Imagínese si tuviera que nombrar todos estos bloques)
2) #lambda se usa generalmente para crear clojures:
def generate_multiple_proc(cofactor)
lambda { |element| element * cofactor }
end
[1, 2, 3, 4].map(&generate_multiple_proc(2)) # => [2, 3, 5, 8]
[1, 2, 3, 4].map(&generate_multiple_proc(3)) # => [3, 6, 9, 12]
Todo se reduce al estilo. Las lambdas son un estilo declarativo, los métodos son un estilo imperativo. Considera esto:
Lambda, bloques, procs, son todos tipos diferentes de cierre. Ahora la pregunta es cuándo y por qué utilizar un cierre anónimo. Puedo responder eso, al menos en ruby!
Los cierres contienen el contexto léxico de donde fueron llamados. Si llama a un método desde dentro de un método, no obtiene el contexto de donde se llamó el método. Esto se debe a la forma en que se almacena la cadena de objetos en el AST.
Un cierre (lambda) por otro lado, se puede pasar CON contexto léxico a través de un método, lo que permite una evaluación lenta.
También las lambdas se prestan naturalmente a la recursión y la enumeración.
En el caso de OOP, debe crear una función en una clase solo si debe haber tal operación en la clase de acuerdo con su modelado de dominio.
Si necesita una función rápida que se pueda escribir en línea, por ejemplo, para comparar, use una lambda
También verifique estas publicaciones SO -
Cuándo usar lambda, cuándo usar Proc.new?