Acabo de aprender sobre el curry, y aunque creo que entiendo el concepto, no veo ninguna gran ventaja en usarlo.
Como ejemplo trivial, uso una función que agrega dos valores (escritos en ML). La versión sin curry sería
fun add(x, y) = x + y
y se llamaría como
add(3, 5)
mientras que la versión al curry es
fun add x y = x + y
(* short for val add = fn x => fn y=> x + y *)
y se llamaría como
add 3 5
Me parece que es solo el azúcar sintáctico que elimina un conjunto de paréntesis para definir y llamar a la función. He visto al curry listado como una de las características importantes de un lenguaje funcional, y estoy un poco decepcionado por el momento. El concepto de crear una cadena de funciones que consuma cada parámetro individual, en lugar de una función que tome una tupla, parece bastante complicado de usar para un simple cambio de sintaxis.
¿Es la sintaxis un poco más simple la única motivación para el curry, o me estoy perdiendo algunas otras ventajas que no son obvias en mi ejemplo tan simple? ¿El curry es solo azúcar sintáctico?
fuente
Respuestas:
Con las funciones currificadas, puede reutilizar más fácilmente funciones más abstractas, ya que puede especializarse. Digamos que tienes una función de suma
y que desea agregar 2 a cada miembro de una lista. En Haskell harías esto:
Aquí la sintaxis es más clara que si tuviera que crear una función
add2
o si tuvieras que hacer una función lambda anónima:
También le permite abstraerse de diferentes implementaciones. Digamos que tenía dos funciones de búsqueda. Uno de una lista de pares clave / valor y una clave para un valor y otro de un mapa de claves a valores y una clave a un valor, así:
Entonces podría hacer una función que aceptara una función de búsqueda de Clave a Valor. Puede pasarle cualquiera de las funciones de búsqueda anteriores, parcialmente aplicadas con una lista o un mapa, respectivamente:
En conclusión: el curry es bueno, porque te permite especializar / aplicar parcialmente las funciones usando una sintaxis ligera y luego pasar estas funciones parcialmente aplicadas a una función de orden superior como
map
ofilter
. Las funciones de orden superior (que toman las funciones como parámetros o las producen como resultados) son la base de la programación funcional, y las funciones de currificación y parcialmente aplicadas permiten que las funciones de orden superior se utilicen de manera mucho más eficaz y concisa.fuente
La respuesta práctica es que el curry hace que la creación de funciones anónimas sea mucho más fácil. Incluso con una sintaxis lambda mínima, es una especie de victoria; comparar:
Si tiene una sintaxis lambda fea, es aún peor. (Te estoy mirando, JavaScript, Scheme y Python).
Esto se vuelve cada vez más útil a medida que utiliza más y más funciones de orden superior. Si bien utilizo más funciones de orden superior en Haskell que en otros idiomas, descubrí que realmente uso menos la sintaxis lambda porque algo así como dos tercios de las veces, la lambda sería una función parcialmente aplicada. (Y la mayor parte del tiempo lo extraigo en una función con nombre).
Más fundamentalmente, no siempre es obvio qué versión de una función es "canónica". Por ejemplo, toma
map
. El tipo demap
se puede escribir de dos maneras:¿Cuál es el "correcto"? En realidad es difícil de decir. En la práctica, la mayoría de los idiomas usan el primero: el mapa toma una función y una lista y devuelve una lista. Sin embargo, fundamentalmente, lo que hace realmente map es asignar funciones normales a funciones de lista: toma una función y devuelve una función. Si el mapa tiene curry, no tiene que responder esta pregunta: hace ambas cosas de una manera muy elegante.
Esto se vuelve especialmente importante una vez que generaliza
map
a otros tipos además de la lista.Además, curry realmente no es muy complicado. En realidad, es una pequeña simplificación sobre el modelo que usan la mayoría de los idiomas: no necesita ninguna noción de funciones de múltiples argumentos incorporados en su idioma. Esto también refleja el cálculo lambda subyacente más de cerca.
Por supuesto, los lenguajes de estilo ML no tienen una noción de argumentos múltiples en forma de curry o sin curry. La
f(a, b, c)
sintaxis en realidad corresponde a pasar la tupla(a, b, c)
af
, por lo quef
solo toma argumentos. Esta es en realidad una distinción muy útil que desearía que otros idiomas tuvieran porque hace que sea muy natural escribir algo como:¡No podría hacer esto fácilmente con lenguajes que tengan la idea de múltiples argumentos integrados!
fuente
El curry puede ser útil si tiene una función que está pasando como un objeto de primera clase, y no recibe todos los parámetros necesarios para evaluarlo en un lugar en el código. Simplemente puede aplicar uno o más parámetros cuando los obtenga y pasar el resultado a otra pieza de código que tenga más parámetros y terminar de evaluarlo allí.
El código para lograr esto será más simple que si primero necesita reunir todos los parámetros.
Además, existe la posibilidad de reutilizar más código, ya que las funciones que toman un solo parámetro (otra función currificada) no tienen que coincidir específicamente con todos los parámetros.
fuente
La principal motivación (al menos inicialmente) para el curry no era práctica sino teórica. En particular, el currículum le permite obtener efectivamente funciones de múltiples argumentos sin definir realmente la semántica para ellos o definir la semántica para los productos. Esto conduce a un lenguaje más simple con tanta expresividad como otro lenguaje más complicado, por lo que es deseable.
fuente
(Daré ejemplos en Haskell).
Al usar lenguajes funcionales, es muy conveniente que pueda aplicar parcialmente una función. Como en Haskell's,
(== x)
es una función que regresaTrue
si su argumento es igual a un término dadox
:sin curry, tendríamos un código algo menos legible:
Esto está relacionado con la programación Tacit (ver también el estilo Pointfree en Haskell wiki). Este estilo no se centra en los valores representados por las variables, sino en las funciones de composición y cómo fluye la información a través de una cadena de funciones. Podemos convertir nuestro ejemplo en un formulario que no use variables en absoluto:
Aquí vemos
==
como una función dea
aa -> Bool
yany
en función dea -> Bool
a[a] -> Bool
. Simplemente componiéndolos, obtenemos el resultado. Todo esto es gracias al curry.Lo contrario, sin curry, también es útil en algunas situaciones. Por ejemplo, supongamos que queremos dividir una lista en dos partes: elementos que son menores que 10 y el resto, y luego concatenar esas dos listas. La división de la lista se realiza por (aquí también usamos curry ). El resultado es de tipo . En lugar de extraer el resultado en su primera y segunda parte y combinarlos usando , podemos hacer esto directamente descurriendo como
partition
(< 10)
<
([Int],[Int])
++
++
De hecho,
(uncurry (++) . partition (< 10)) [4,12,11,1]
evalúa a[4,1,12,11]
.También hay importantes ventajas teóricas:
(a, b) -> c
aa -> (b -> c)
significa que el resultado de la última función es de tipob -> c
. En otras palabras, el resultado es una función.fuente
mem x lst = any (\y -> y == x) lst
? (Con una barra invertida).¡El curry no es solo azúcar sintáctico!
Considere las firmas tipo de
add1
(sin curry) yadd2
(con curry):(En ambos casos, los paréntesis en la firma de tipo son opcionales, pero los he incluido para mayor claridad).
add1
es una función que toma una 2-tupla deint
yint
y devuelve unint
.add2
es una función que toma unint
y devuelve otra función que a su vez toma unint
y devuelve unint
.La diferencia esencial entre los dos se hace más visible cuando especificamos la aplicación de funciones explícitamente. Definamos una función (no curry) que aplique su primer argumento a su segundo argumento:
Ahora podemos ver la diferencia entre
add1
yadd2
más claramente.add1
se llama con una tupla de 2:pero
add2
se llama con unint
y luego su valor de retorno se llama con otroint
:EDITAR: El beneficio esencial del curry es que obtienes una aplicación parcial de forma gratuita. Digamos que desea una función de tipo
int -> int
(digamos,map
sobre una lista) que agregó 5 a su parámetro. ¡Podría escribiraddFiveToParam x = x+5
, o podría hacer el equivalente con una lambda en línea, pero también podría escribir con mucha más facilidad (especialmente en casos menos triviales que este)add2 5
!fuente
El curry es solo azúcar sintáctico, pero creo que estás malentendiendo un poco lo que hace el azúcar. Tomando tu ejemplo,
en realidad es azúcar sintáctica para
Es decir, (agregar x) devuelve una función que toma un argumento y, y agrega x a y.
Esa es una función que toma una tupla y agrega sus elementos. Esas dos funciones son en realidad bastante diferentes; Toman diferentes argumentos.
Si desea agregar 2 a todos los números en una lista:
El resultado sería
[3,4,5]
.Si desea sumar cada tupla en una lista, por otro lado, la función addTuple se ajusta perfectamente.
El resultado sería
[12,13,14]
.Las funciones curry son excelentes cuando la aplicación parcial es útil, por ejemplo, mapa, pliegue, aplicación, filtro. Considere esta función, que devuelve el número positivo más grande en la lista suministrada, o 0 si no hay números positivos:
fuente
Otra cosa que no he visto mencionada todavía es que el curry permite la abstracción (limitada) sobre arity.
Considere estas funciones que son parte de la biblioteca de Haskell.
En cada caso, la variable de tipo
c
puede ser un tipo de función para que estas funciones funcionen en algún prefijo de la lista de parámetros de su argumento. Sin curry, necesitaría una función de lenguaje especial para abstraer sobre la función arity o tener muchas versiones diferentes de estas funciones especializadas para diferentes arities.fuente
Mi comprensión limitada es tal:
1) Aplicación de función parcial
Aplicación de función parcial es el proceso de devolver una función que toma un número menor de argumentos. Si proporciona 2 de 3 argumentos, devolverá una función que toma 3-2 = 1 argumento. Si proporciona 1 de 3 argumentos, devolverá una función que toma 3-1 = 2 argumentos. Si lo desea, incluso podría aplicar parcialmente 3 de 3 argumentos y devolvería una función que no requiere argumento.
Entonces, dada la siguiente función:
Al vincular 1 a x y aplicarlo parcialmente a la función anterior
f(x,y,z)
, obtendría:Dónde:
f'(y,z) = 1 + y + z;
Ahora, si tuviera que unir y a 2 yz a 3, y aplicar parcialmente
f'(y,z)
, obtendría:Dónde:
f''() = 1 + 2 + 3
;Ahora, en cualquier momento, puede elegir evaluar
f
,f'
of''
. Entonces puedo hacer:o
2) curry
Curry, por otro lado, es el proceso de dividir una función en una cadena anidada de funciones de un argumento. Nunca puede proporcionar más de 1 argumento, es uno o cero.
Entonces dada la misma función:
Si lo curriesas, obtendrás una cadena de 3 funciones:
Dónde:
Ahora si llamas
f'(x)
conx = 1
:Se le devuelve una nueva función:
Si llamas
g(y)
cony = 2
:Se le devuelve una nueva función:
Finalmente si llamas
h(z)
conz = 3
:Volverá
6
.3) cierre
Finalmente, el cierre es el proceso de capturar una función y datos juntos como una sola unidad. Un cierre de función puede llevar de 0 a un número infinito de argumentos, pero también es consciente de los datos que no se le pasan.
Nuevamente, dada la misma función:
En su lugar, puede escribir un cierre:
Dónde:
f'
está cerrado elx
. Lo que significa quef'
puede leer el valor de x que está dentrof
.Entonces, si tuviera que llamar
f
conx = 1
:Tendrían un cierre:
Ahora si llamaste
closureOfF
cony = 2
yz = 3
:Que volvería
6
Conclusión
El curry, la aplicación parcial y los cierres son algo similares, ya que descomponen una función en más partes.
El curry descompone una función de múltiples argumentos en funciones anidadas de argumentos individuales que devuelven funciones de argumentos individuales. No tiene sentido cursar una función de uno o menos argumentos, ya que no tiene sentido.
La aplicación parcial descompone una función de argumentos múltiples en una función de argumentos menores cuyos argumentos ahora faltantes fueron sustituidos por el valor proporcionado.
El cierre descompone una función en una función y un conjunto de datos donde las variables dentro de la función que no se pasaron pueden mirar dentro del conjunto de datos para encontrar un valor al cual unirse cuando se le pide que evalúe.
Lo que es confuso acerca de todo esto es que pueden usarse para implementar un subconjunto de los demás. Entonces, en esencia, todos son un poco un detalle de implementación. Todos proporcionan un valor similar en el sentido de que no necesita reunir todos los valores por adelantado y en que puede reutilizar parte de la función, ya que la ha descompuesto en unidades discretas.
Divulgar
De ninguna manera soy un experto en el tema, solo recientemente comencé a aprender sobre esto, por lo que proporciono mi comprensión actual, pero podría tener errores que invito a señalar y corregiré como / si Descubro alguno.
fuente
Curry (aplicación parcial) le permite crear una nueva función a partir de una función existente mediante la fijación de algunos parámetros. Es un caso especial de cierre léxico donde la función anónima es solo un contenedor trivial que pasa algunos argumentos capturados a otra función. También podemos hacer esto usando la sintaxis general para hacer cierres léxicos, pero la aplicación parcial proporciona un azúcar sintáctico simplificado.
Es por eso que los programadores de Lisp, cuando trabajan en un estilo funcional, a veces usan bibliotecas para aplicaciones parciales .
En lugar de
(lambda (x) (+ 3 x))
, lo que nos da una función que agrega 3 a su argumento, puede escribir algo así(op + 3)
, por lo que agregar 3 a cada elemento de una lista sería algo(mapcar (op + 3) some-list)
más que(mapcar (lambda (x) (+ 3 x)) some-list)
. Estaop
macro te hará una función que toma algunos argumentosx y z ...
e invocaciones(+ a x y z ...)
.En muchos lenguajes puramente funcionales, la aplicación parcial está arraigada en la sintaxis para que no haya
op
operador. Para desencadenar una aplicación parcial, simplemente llame a una función con menos argumentos de los que requiere. En lugar de producir un"insufficient number of arguments"
error, el resultado es una función de los argumentos restantes.fuente
a -> b -> c
no tiene parámetro s (en plural), que tiene un solo parámetro,c
. Cuando se llama, devuelve una función de tipoa -> b
.Para la función
Es de la forma
f': 'a * 'b -> 'c
Para evaluar uno hará
Para la función curry
Para evaluar uno hará
Donde es un cálculo parcial, específicamente (3 + y), que luego se puede completar el cálculo con
agregar en el segundo caso es de la forma
f: 'a -> 'b -> 'c
Lo que está haciendo el curry aquí es transformar una función que toma dos acuerdos en uno que solo requiere que uno devuelva un resultado. Evaluación parcial
Digamos
x
en el RHS no es solo un int regular, sino un cálculo complejo que lleva un tiempo completar, por aumentos, por el bien, dos segundos.Entonces la función ahora se ve como
De tipo
add : int * int -> int
Ahora queremos calcular esta función para un rango de números, vamos a mapearla
Para lo anterior, el resultado de
twoSecondsComputation
se evalúa cada vez. Esto significa que toma 6 segundos para este cálculo.Usar una combinación de puesta en escena y curry puede evitar esto.
De la forma curry
add : int -> int -> int
Ahora uno puede hacer,
El
twoSecondsComputation
único necesita ser evaluado una vez. Para subir la escala, reemplace dos segundos con 15 minutos, o cualquier hora, luego tenga un mapa contra 100 números.Resumen : El curry es excelente cuando se usa con otros métodos para funciones de nivel superior como una herramienta de evaluación parcial. Su propósito no puede demostrarse realmente por sí mismo.
fuente
El curry permite una composición flexible de funciones.
Hice una función "curry". En este contexto, no me importa qué tipo de registrador obtengo o de dónde proviene. No me importa cuál es la acción o de dónde viene. Todo lo que me importa es procesar mi entrada.
La variable de construcción es una función que devuelve una función que devuelve una función que toma mi entrada que hace mi trabajo. Este es un ejemplo útil simple y no un objeto a la vista.
fuente
El curry es una ventaja cuando no tienes todos los argumentos para una función. Si está evaluando completamente la función, entonces no hay una diferencia significativa.
Curry te permite evitar mencionar parámetros aún no necesarios. Es más conciso y no requiere encontrar un nombre de parámetro que no choque con otra variable de alcance (que es mi beneficio favorito).
Por ejemplo, cuando utiliza funciones que toman funciones como argumentos, a menudo se encontrará en situaciones en las que necesita funciones como "agregar 3 a la entrada" o "comparar entrada a la variable v". Con el curry, estas funciones se escriben fácilmente:
add 3
y(== v)
. Sin curry, tienes que usar expresiones lambda:x => add 3 x
yx => x == v
. Las expresiones lambda son dos veces más largas y tienen una pequeña cantidad de trabajo ocupado relacionado con elegir un nombre además dex
si ya hay unx
alcance.Una ventaja adicional de los lenguajes basados en el currículum es que, al escribir código genérico para funciones, no terminas con cientos de variantes basadas en el número de parámetros. Por ejemplo, en C #, un método 'curry' necesitaría variantes para Func <R>, Func <A, R>, Func <A1, A2, R>, Func <A1, A2, A3, R>, etc. Siempre. En Haskell, el equivalente de una Func <A1, A2, R> es más como una Func <Tupla <A1, A2>, R> o una Func <A1, Func <A2, R >> (y una Func <R> es más como una Func <Unidad, R>), por lo que todas las variantes corresponden al caso Func <A, R>.
fuente
El razonamiento principal en el que puedo pensar (y no soy un experto en este tema de ninguna manera) comienza a mostrar sus beneficios a medida que las funciones se mueven de trivial a no trivial. En todos los casos triviales con la mayoría de los conceptos de esta naturaleza, no encontrará ningún beneficio real. Sin embargo, la mayoría de los lenguajes funcionales hacen un uso intensivo de la pila en las operaciones de procesamiento. Considere PostScript o Lisp como ejemplos de esto. Al utilizar el curry, las funciones se pueden apilar de manera más efectiva y este beneficio se hace evidente a medida que las operaciones se vuelven cada vez menos triviales. De manera curiosa, el comando y los argumentos pueden arrojarse a la pila en orden y retirarse según sea necesario para que se ejecuten en el orden correcto.
fuente
El curry depende de manera crucial (definitivamente, incluso) de la capacidad de devolver una función.
Considere este pseudocódigo (artificial).
var f = (m, x, b) => ... devolver algo ...
Supongamos que llamar a f con menos de tres argumentos devuelve una función.
var g = f (0, 1); // esto devuelve una función vinculada a 0 y 1 (myx) que acepta un argumento más (b).
var y = g (42); // invoque g con el tercer argumento faltante, usando 0 y 1 para myx
Es bastante útil (y SECO) que pueda aplicar parcialmente los argumentos y recuperar una función reutilizable (vinculada a los argumentos que proporcionó).
fuente