¿Cuál es la ventaja de curry?

154

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?

Científico loco
fuente
54
El curry solo es esencialmente inútil, pero tener todas las funciones curry por defecto hace que muchas otras características sean mucho más agradables de usar. Es difícil apreciar esto hasta que haya usado un lenguaje funcional por un tiempo.
CA McCann
44
Algo que se mencionó al pasar por delnan en un comentario sobre la respuesta de JoelEtherton, pero que pensé que mencionaría explícitamente, es que (al menos en Haskell) puede aplicar parcialmente no solo con funciones sino también con constructores de tipos, esto puede ser bastante práctico; Esto podría ser algo en lo que pensar.
Paul
Todos han dado ejemplos de Haskell. Uno puede preguntarse que el curry es útil solo en Haskell.
Manoj R
@ManojR Todos no han dado ejemplos en Haskell.
phwd
1
La pregunta generó una discusión bastante interesante sobre Reddit .
Yannis

Respuestas:

126

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

add x y = x + y

y que desea agregar 2 a cada miembro de una lista. En Haskell harías esto:

map (add 2) [1, 2, 3] -- gives [3, 4, 5]
-- actually one could just do: map (2+) [1, 2, 3], but that may be Haskell specific

Aquí la sintaxis es más clara que si tuviera que crear una función add2

add2 y = add 2 y
map add2 [1, 2, 3]

o si tuvieras que hacer una función lambda anónima:

map (\y -> 2 + y) [1, 2, 3]

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í:

lookup1 :: [(Key, Value)] -> Key -> Value -- or perhaps it should be Maybe Value
lookup2 :: Map Key Value -> Key -> Value

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:

myFunc :: (Key -> Value) -> .....

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 mapo filter. 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.

Boris
fuente
31
Vale la pena señalar que, debido a esto, el orden de argumento utilizado para las funciones en Haskell a menudo se basa en la probabilidad de una aplicación parcial, lo que a su vez hace que los beneficios descritos anteriormente se apliquen (ha, ha) en más situaciones. El currículum por defecto termina siendo aún más beneficioso de lo que parece a partir de ejemplos específicos como los de aquí.
CA McCann
wat. "Uno de una lista de pares clave / valor y una clave de un valor y otro de un mapa de claves a valores y una clave a un valor"
Mateen Ulhaq
@MateenUlhaq Es una continuación de la oración anterior, donde supongo que queremos obtener un valor basado en una clave, y tenemos dos formas de hacerlo. La oración enumera esas dos formas. En la primera forma, se le da una lista de pares clave / valor y la clave para la que queremos encontrar el valor, y de otra manera se nos da un mapa apropiado, y nuevamente una clave. Puede ser útil mirar el código inmediatamente después de la oración.
Boris
53

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:

map (add 1) [1..10]
map (\ x -> add 1 x) [1..10]

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 de mapse puede escribir de dos maneras:

map :: (a -> b) -> [a] -> [b]
map :: (a -> b) -> ([a] -> [b])

¿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 mapa 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)a f, por lo que fsolo 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:

map f [(1,2,3), (4,5,6), (7, 8, 9)]

¡No podría hacer esto fácilmente con lenguajes que tengan la idea de múltiples argumentos integrados!

Tikhon Jelvis
fuente
1
"Los lenguajes de estilo ML no tienen una noción de argumentos múltiples en curry o sin cursar": a este respecto, ¿es Haskell ML-style?
Giorgio
1
@ Jorge: Sí.
Tikhon Jelvis
1
Interesante. Conozco algunos Haskell y estoy aprendiendo SML en este momento, por lo que es interesante ver las diferencias y similitudes entre los dos idiomas.
Giorgio
Gran respuesta, y si aún no está convencido, piense en las tuberías de Unix que son similares a las transmisiones lambda
Sridhar Sarnobat
La respuesta "práctica" no es muy relevante porque la verbosidad generalmente se evita mediante la aplicación parcial , no al curry. Y argumentaría aquí que la sintaxis de la abstracción lambda (a pesar de la declaración de tipo) es más fea que eso (al menos) en Scheme, ya que necesita más reglas sintácticas especiales incorporadas para analizarla correctamente, lo que aumenta la especificación del lenguaje sin ninguna ganancia sobre propiedades semánticas.
FrankHB
24

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.

psr
fuente
14

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.

Alex R
fuente
2
Si bien la motivación aquí es teórica, creo que la simplicidad es casi siempre una ventaja práctica también. No preocuparme por las funciones de múltiples argumentos me hace la vida más fácil cuando programo, tal como lo haría si estuviera trabajando con la semántica.
Tikhon Jelvis
2
@TikhonJelvis Sin embargo, cuando estás programando, el curry te da otras cosas de qué preocuparte, como que el compilador no capta el hecho de que pasaste muy pocos argumentos a una función, o incluso recibe un mensaje de error incorrecto en ese caso; cuando no usas curry, el error es mucho más evidente.
Alex R
Nunca he tenido problemas como ese: GHC, al menos, es muy bueno en ese sentido. El compilador siempre detecta ese tipo de problema y también tiene buenos mensajes de error para este error.
Tikhon Jelvis
1
No puedo aceptar que los mensajes de error califiquen como buenos. Reparable, sí, pero todavía no son buenos. También solo detecta ese tipo de problema si da como resultado un error de tipo, es decir, si luego intenta usar el resultado como algo diferente a una función (o ha escrito anotado, pero confiar en eso para errores legibles tiene sus propios problemas ); La ubicación informada del error está divorciada de su ubicación real.
Alex R
14

(Daré ejemplos en Haskell).

  1. Al usar lenguajes funcionales, es muy conveniente que pueda aplicar parcialmente una función. Como en Haskell's, (== x)es una función que regresa Truesi su argumento es igual a un término dado x:

    mem :: Eq a => a -> [a] -> Bool
    mem x lst = any (== x) lst
    

    sin curry, tendríamos un código algo menos legible:

    mem x lst = any (\y -> y == x) lst
    
  2. 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:

    mem = any . (==)
    

    Aquí vemos ==como una función de aa a -> Booly anyen función de a -> Boola [a] -> Bool. Simplemente componiéndolos, obtenemos el resultado. Todo esto es gracias al curry.

  3. 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 comopartition (< 10)<([Int],[Int])++++

    uncurry (++) . partition (< 10)
    

De hecho, (uncurry (++) . partition (< 10)) [4,12,11,1]evalúa a [4,1,12,11].

También hay importantes ventajas teóricas:

  1. El curry es esencial para los idiomas que carecen de tipos de datos y solo tienen funciones, como el cálculo lambda . Si bien estos lenguajes no son útiles para el uso práctico, son muy importantes desde un punto de vista teórico.
  2. Esto está conectado con la propiedad esencial de los lenguajes funcionales: las funciones son objetos de primera clase. Como hemos visto, la conversión de (a, b) -> ca a -> (b -> c)significa que el resultado de la última función es de tipo b -> c. En otras palabras, el resultado es una función.
  3. (Un) curry está estrechamente relacionado con las categorías cerradas cartesianas , que es una forma categórica de ver los cálculos lambda mecanografiados.
Petr Pudlák
fuente
Para el bit de "código mucho menos legible", ¿no debería ser así mem x lst = any (\y -> y == x) lst? (Con una barra invertida).
stusmith
Sí, gracias por señalarlo, lo corregiré.
Petr Pudlák
9

¡El curry no es solo azúcar sintáctico!

Considere las firmas tipo de add1(sin curry) y add2(con curry):

add1 : (int * int) -> int
add2 : int -> (int -> int)

(En ambos casos, los paréntesis en la firma de tipo son opcionales, pero los he incluido para mayor claridad).

add1es una función que toma una 2-tupla de inty inty devuelve un int. add2es una función que toma un inty devuelve otra función que a su vez toma un inty devuelve un int.

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:

apply(f, b) = f b

Ahora podemos ver la diferencia entre add1y add2más claramente. add1se llama con una tupla de 2:

apply(add1, (3, 5))

pero add2se llama con un int y luego su valor de retorno se llama con otroint :

apply(apply(add2, 3), 5)

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, mapsobre una lista) que agregó 5 a su parámetro. ¡Podría escribir addFiveToParam 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!

Llama de Ptharien
fuente
3
Entiendo que hay una gran diferencia detrás de escena para mi ejemplo, pero el resultado parece ser un simple cambio sintáctico.
Científico loco
55
El curry no es un concepto muy profundo. Se trata de simplificar el modelo subyacente (ver cálculo Lambda) o en idiomas que tienen tuplas de todos modos, de hecho se trata de la conveniencia sintáctica de la aplicación parcial. No subestimes la importancia de la conveniencia sintáctica.
Pico
9

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,

fun add x y = x + y

en realidad es azúcar sintáctica para

fun add x = fn y => x + y

Es decir, (agregar x) devuelve una función que toma un argumento y, y agrega x a y.

fun addTuple (x, y) = x + 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:

(* add 2 to all numbers using the uncurried function *)
map (fn x => addTuple (x, 2)) [1,2,3]
(* using the curried function *)
map (add 2) [1,2,3]

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.

(* Sum each tuple using the uncurried function *)
map addTuple [(10,2), (10,3), (10,4)]    
(* sum each tuple using curried function *)
map (fn (a,b) => add a b) [(10,2), (10,3), (10,4)]

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:

- val highestPositive = foldr Int.max 0;   
val highestPositive = fn : int list -> int 
GNUD
fuente
1
Comprendí que la función currificada tiene una firma de tipo diferente, y que en realidad es una función que devuelve otra función. Sin embargo, me faltaba la parte de la aplicación parcial.
Científico loco
9

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.

(.) :: (b -> c) -> (a -> b) -> a -> c
either :: (a -> c) -> (b -> c) -> Either a b -> c
flip :: (a -> b -> c) -> b -> a -> c
on :: (b -> b -> c) -> (a -> b) -> a -> a -> c

En cada caso, la variable de tipo cpuede 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.

Geoff Reedy
fuente
6

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:

f(x,y,z) = x + y + z;

Al vincular 1 a x y aplicarlo parcialmente a la función anterior f(x,y,z), obtendría:

f(1,y,z) = f'(y,z);

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:

f'(2,3) = f''();

Dónde: f''() = 1 + 2 + 3;

Ahora, en cualquier momento, puede elegir evaluar f, f'o f''. Entonces puedo hacer:

print(f''()) // and it would return 6;

o

print(f'(1,1)) // and it would return 3;

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:

f(x,y,z) = x + y + z;

Si lo curriesas, obtendrás una cadena de 3 funciones:

f'(x) -> f''(y) -> f'''(z)

Dónde:

f'(x) = x + f''(y);

f''(y) = y + f'''(z);

f'''(z) = z;

Ahora si llamas f'(x)con x = 1:

f'(1) = 1 + f''(y);

Se le devuelve una nueva función:

g(y) = 1 + f''(y);

Si llamas g(y)con y = 2:

g(2) = 1 + 2 + f'''(z);

Se le devuelve una nueva función:

h(z) = 1 + 2 + f'''(z);

Finalmente si llamas h(z)con z = 3:

h(3) = 1 + 2 + 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:

f(x,y,z) = x + y + z;

En su lugar, puede escribir un cierre:

f(x) = x + f'(y, z);

Dónde:

f'(y,z) = x + y + z;

f'está cerrado el x. Lo que significa que f'puede leer el valor de x que está dentro f.

Entonces, si tuviera que llamar fcon x = 1:

f(1) = 1 + f'(y, z);

Tendrían un cierre:

closureOfF(y, z) =
                   var x = 1;
                   f'(y, z);

Ahora si llamaste closureOfFcon y = 2y z = 3:

closureOfF(2, 3) = 
                   var x = 1;
                   x + 2 + 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.

Didier A.
fuente
1
Entonces la respuesta es: ¿curry no tiene ventaja?
ceving
1
@ceving Hasta donde yo sé, eso es correcto. En la práctica, el curry y la aplicación parcial le darán los mismos beneficios. La elección de cuál implementar en un idioma se hace por razones de implementación, uno podría ser más fácil de implementar que otro dado un determinado idioma.
Didier A.
5

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). Esta opmacro te hará una función que toma algunos argumentos x 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 opoperador. 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.

Kaz
fuente
"Currying ... le permite crear una nueva función ... fijando algunos parámetros" - no, en función del tipo a -> b -> cno tiene parámetro s (en plural), que tiene un solo parámetro, c. Cuando se llama, devuelve una función de tipo a -> b.
Max Heiber
4

Para la función

fun add(x, y) = x + y

Es de la forma f': 'a * 'b -> 'c

Para evaluar uno hará

add(3, 5)
val it = 8 : int

Para la función curry

fun add x y = x + y

Para evaluar uno hará

add 3
val it = fn : int -> int

Donde es un cálculo parcial, específicamente (3 + y), que luego se puede completar el cálculo con

it 5
val it = 8 : int

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

¿Por qué uno necesitaría esto?

Digamos xen 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.

x = twoSecondsComputation(z)

Entonces la función ahora se ve como

fun add (z:int) (y:int) : int =
    let
        val x = twoSecondsComputation(z)
    in
        x + y
    end;

De tipo add : int * int -> int

Ahora queremos calcular esta función para un rango de números, vamos a mapearla

val result1 = map (fn x => add (20, x)) [3, 5, 7];

Para lo anterior, el resultado de twoSecondsComputationse 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.

fun add (z:int) : int -> int =
    let
        val x = twoSecondsComputation(z)
    in
        (fn y => x + y)
    end;

De la forma curry add : int -> int -> int

Ahora uno puede hacer,

val add' = add 20;
val result2 = map add' [3, 5, 7, 11, 13];

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.

phwd
fuente
3

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.

var builder = curry(function(input, logger, action) {
     logger.log("Starting action");
     try {
         action(input);
         logger.log("Success!");
     }
     catch (err) {
         logger.logerror("Boo we failed..", err);
     }
});
var x = "My input.";
goGatherArgs(builder)(x); // Supplies action first, then logger somewhere.

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.

mortalapeman
fuente
2

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 3y (== v). Sin curry, tienes que usar expresiones lambda: x => add 3 xy x => 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 de xsi ya hay un xalcance.

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

Craig Gidney
fuente
2

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.

Peter Mortensen
fuente
1
¿Cómo exactamente requiere la creación de muchos más marcos de pila para hacer las cosas más eficientes?
Mason Wheeler
1
@MasonWheeler: No lo sabría, ya que dije que no soy un experto en lenguajes funcionales o currículum específicamente. Etiqueté esta wiki de la comunidad específicamente por eso.
Joel Etherton
44
@MasonWheeler Tu tienes un punto en la redacción de esta respuesta, pero permíteme agregar y decir que la cantidad de marcos de pila realmente creados depende mucho de la implementación. Por ejemplo, en la máquina G sin etiqueta sin espinas (STG; la forma en que GHC implementa Haskell) retrasa la evaluación real hasta que acumula todos (o al menos tantos como se sabe que se requieren) argumentos. Parece que no puedo recordar si esto se hace para todas las funciones o solo para los constructores, pero creo que debería ser posible para la mayoría de las funciones. (Por otra parte, el concepto de "marcos de pila" no se aplica realmente a la STG.)
1

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

Rick O'Shea
fuente