¿Cuál es la diferencia entre currying y aplicación parcial?

A menudo veo en Internet varias quejas de que los ejemplos de otros pueblos de currying no son currying, pero en realidad son solo aplicaciones parciales.

No he encontrado una explicación decente de qué aplicación parcial es, o cómo difiere de currying. Parece haber una confusión general, con ejemplos equivalentes que se describen como currying en algunos lugares y aplicación parcial en otros.

¿Podría alguien proporcionarme una definición de ambos términos y detalles de cómo difieren?

Currying consiste en convertir una función de n argumentos en n funciones con un único argumento cada uno. Dada la siguiente función:

function f(x,y,z) { z(x(y));} 

Cuando curry, se convierte en:

 function f(x) { lambda(y) { lambda(z) { z(x(y)); } } } 

Para obtener la aplicación completa de f (x, y, z), debe hacer esto:

 f(x)(y)(z); 

Muchos lenguajes funcionales le permiten escribir fxyz . Si solo llamas a fxy o f (x) (y) , obtienes una función parcialmente aplicada: el valor de retorno es un cierre de lambda(z){z(x(y))} con los valores de x y y a f(x,y) .

Una forma de usar la aplicación parcial es definir funciones como aplicaciones parciales de funciones generalizadas, como fold :

 function fold(combineFunction, accumalator, list) {/* ... */} function sum = curry(fold)(lambda(accum,e){e+accum}))(0); function length = curry(fold)(lambda(accum,_){1+accum})(empty-list); function reverse = curry(fold)(lambda(accum,e){concat(e,accum)})(empty-list); /* ... */ @list = [1, 2, 3, 4] sum(list) //returns 10 @f = fold(lambda(accum,e){e+accum}) //f = lambda(accumaltor,list) {/*...*/} f(0,list) //returns 10 @g = f(0) //same as sum g(list) //returns 10 

La forma más fácil de ver cómo difieren es considerar un ejemplo real . Supongamos que tenemos una función Add que toma 2 números como entrada y devuelve un número como salida, por ejemplo, Add(7, 5) devuelve 12 . En este caso:

  • La aplicación parcial de la función Add con un valor 7 nos dará una nueva función como salida. Esa función toma 1 número como entrada y emite un número. Como tal:

     Partial(Add, 7); // returns a function f2 as output // f2 takes 1 number as input and returns a number as output 

    Entonces podemos hacer esto:

     f2 = Partial(Add, 7); f2(5); // returns 12; // f2(7)(5) is just a syntactic shortcut 
  • Al currar la función Add obtendremos una nueva función como salida. Esa función en sí toma 1 número como entrada y emite otra función nueva. Esa tercera función luego toma 1 número como entrada y devuelve un número como salida. Como tal:

     Curry(Add); // returns a function f2 as output // f2 takes 1 number as input and returns a function f3 as output // ie f2(number) = f3 // f3 takes 1 number as input and returns a number as output // ie f3(number) = number 

    Entonces podemos hacer esto:

     f2 = Curry(Add); f3 = f2(7); f3(5); // returns 12 

En otras palabras, “currying” y “aplicación parcial” son dos funciones totalmente diferentes. Currying toma exactamente 1 entrada, mientras que la aplicación parcial toma 2 (o más) entradas.

Aunque ambos devuelven una función como salida, las funciones devueltas son de formas totalmente diferentes, como se demostró anteriormente.

Nota: esto fue tomado de F # Basics, un excelente artículo introductorio para los desarrolladores de .NET que entran en la progtwigción funcional.

Currying significa romper una función con muchos argumentos en una serie de funciones que cada una toma un argumento y finalmente produce el mismo resultado que la función original. Currying es probablemente el tema más desafiante para desarrolladores nuevos en progtwigción funcional, particularmente porque a menudo se confunde con aplicaciones parciales. Puede ver ambos en el trabajo en este ejemplo:

 let multiply xy = x * y let double = multiply 2 let ten = double 5 

De inmediato, debería ver un comportamiento que es diferente de la mayoría de los idiomas imperativos. La segunda instrucción crea una nueva función llamada double pasando un argumento a una función que toma dos. El resultado es una función que acepta un argumento int y produce el mismo resultado que si hubieses llamado multiplicar con x igual a 2 e y igual a ese argumento. En términos de comportamiento, es lo mismo que este código:

 let double2 z = multiply 2 z 

A menudo, las personas dicen erróneamente que multiplicar es al curry para formar el doble. Pero esto solo es cierto. La función de multiplicar está al curry, pero eso ocurre cuando está definida porque las funciones en F # están marcadas por defecto. Cuando se crea la función doble, es más exacto decir que la función de multiplicar se aplica parcialmente.

La función de multiplicar es realmente una serie de dos funciones. La primera función toma un argumento int y devuelve otra función, vinculando efectivamente x a un valor específico. Esta función también acepta un argumento int que se puede considerar como el valor para enlazar a y. Después de llamar a esta segunda función, xey están ambos vinculados, por lo que el resultado es el producto de xey como se define en el cuerpo del doble.

Para crear el doble, la primera función en la cadena de funciones de multiplicación se evalúa para aplicar parcialmente multiplicar. La función resultante recibe el nombre doble. Cuando se evalúa el doble, usa su argumento junto con el valor aplicado parcialmente para crear el resultado.

Interesante pregunta. Después de buscar un poco, “La aplicación de función parcial no está currando” dio la mejor explicación que encontré. No puedo decir que la diferencia práctica sea ​​particularmente obvia para mí, pero entonces no soy un experto en FP …

Otra página de aspecto útil (que confieso que aún no he leído) es “Currying and Partial Application with Java Closures” .

Parece que este es un par de términos ampliamente confusos, fíjate.

He respondido esto en otro hilo https://stackoverflow.com/a/12846865/1685865 . En resumen, la aplicación de función parcial se trata de fijar algunos argumentos de una función multivariable dada para producir otra función con menos argumentos, mientras que Currying se trata de convertir una función de N argumentos en una función unaria que devuelve una función única … [Un ejemplo de Currying se muestra al final de esta publicación.]

El currying es principalmente de interés teórico: uno puede express cálculos usando solo funciones unarias (es decir, cada función es única). En la práctica y como subproducto, es una técnica que puede hacer que muchas aplicaciones funcionales parciales útiles (pero no todas) sean triviales, si el lenguaje tiene funciones con curry. Una vez más, no es el único medio para implementar aplicaciones parciales. Por lo tanto, podría encontrar escenarios en los que la aplicación parcial se realiza de otra manera, pero las personas lo están confundiendo con Currying.

(Ejemplo de Currying)

En la práctica, uno no solo escribiría

 lambda x: lambda y: lambda z: x + y + z 

o el javascript equivalente

 function (x) { return function (y){ return function (z){ return x + y + z }}} 

en lugar de

 lambda x, y, z: x + y + z 

por el bien de Currying.

La diferencia entre el curry y la aplicación parcial se puede ilustrar mejor a través de este siguiente ejemplo de JavaScript:

 function f(x, y, z) { return x + y + z; } var partial = f.bind(null, 1); 6 === partial(2, 3); 

La aplicación parcial da como resultado una función de aridad menor; en el ejemplo anterior, f tiene una arity de 3 mientras que partial solo tiene una arity de 2. Más importante aún, una función parcialmente aplicada devolvería el resultado de inmediato al ser invocado , no con otra función en la cadena de currying. Entonces, si está viendo algo como partial(2)(3) , no es una aplicación parcial en realidad.

Otras lecturas:

  • Progtwigción funcional en 5 minutos
  • Currying: contraste con la aplicación de función parcial

Para mí, la aplicación parcial debe crear una nueva función donde los argumentos utilizados estén completamente integrados en la función resultante.

La mayoría de los lenguajes funcionales implementan el currying al devolver un cierre: no se evalúa en lambda cuando se aplica parcialmente. Por lo tanto, para que la aplicación parcial sea interesante, debemos hacer una diferencia entre el currying y la aplicación parcial y considerar la aplicación parcial como currying más evaluación bajo lambda.

Podría estar muy equivocado aquí, ya que no tengo una sólida formación en matemáticas teóricas o progtwigción funcional, pero desde mi breve incursión en FP, parece que currying tiende a convertir una función de N argumentos en N funciones de un argumento, mientras que la aplicación parcial [en la práctica] funciona mejor con funciones variadas con un número indeterminado de argumentos. Sé que algunos de los ejemplos en respuestas anteriores desafían esta explicación, pero me ha ayudado mucho a separar los conceptos. Considere este ejemplo (escrito en CoffeeScript para ser sucinto, mis disculpas si confunde más, pero solicite una aclaración, si es necesario):

 # partial application partial_apply = (func) -> args = [].slice.call arguments, 1 -> func.apply null, args.concat [].slice.call arguments sum_variadic = -> [].reduce.call arguments, (acc, num) -> acc + num add_to_7_and_5 = partial_apply sum_variadic, 7, 5 add_to_7_and_5 10 # returns 22 add_to_7_and_5 10, 11, 12 # returns 45 # currying curry = (func) -> num_args = func.length helper = (prev) -> -> args = prev.concat [].slice.call arguments return if args.length < num_args then helper args else func.apply null, args helper [] sum_of_three = (x, y, z) -> x + y + z curried_sum_of_three = curry sum_of_three curried_sum_of_three 4 # returns a function expecting more arguments curried_sum_of_three(4)(5) # still returns a function expecting more arguments curried_sum_of_three(4)(5)(6) # returns 15 curried_sum_of_three 4, 5, 6 # returns 15 

Obviamente, este es un ejemplo inventado, pero observe que la aplicación parcial de una función que acepta cualquier número de argumentos nos permite ejecutar una función pero con algunos datos preliminares. Currying una función es similar pero nos permite ejecutar una función N-parameter en piezas hasta, pero solo hasta que, todos los N parámetros sean contabilizados.

De nuevo, esta es mi opinión de las cosas que he leído. Si alguien no está de acuerdo, agradecería un comentario sobre por qué, en lugar de un voto negativo inmediato. Además, si el CoffeeScript es difícil de leer, visite coffeescript.org, haga clic en “try coffeescript” y pegue el código para ver la versión comstackda, lo que (con suerte) puede tener más sentido. ¡Gracias!

Tuve esta pregunta mucho mientras aprendía y desde entonces me han preguntado muchas veces. La forma más simple que puedo describir la diferencia es que ambos son los mismos 🙂 Déjenme explicar … obviamente hay diferencias.

Tanto la aplicación parcial como el currying implican el suministro de argumentos a una función, quizás no todos a la vez. Un ejemplo bastante canónico es agregar dos números. En pseudocódigo (en realidad JS sin palabras clave), la función base puede ser la siguiente:

 add = (x, y) => x + y 

Si quisiera una función de “agregar un elemento”, podría aplicarla parcialmente o curry:

 addOneC = curry(add, 1) addOneP = partial(add, 1) 

Ahora usarlos es claro:

 addOneC(2) #=> 3 addOneP(2) #=> 3 

Entonces, ¿cuál es la diferencia? Bueno, es sutil, pero la aplicación parcial implica el suministro de algunos argumentos y la función devuelta ejecutará la función principal en la próxima invocación, mientras que Currying seguirá esperando hasta que tenga todos los argumentos necesarios:

 curriedAdd = curry(add) # notice, no args are provided addOne = curriedAdd(1) # returns a function that can be used to provide the last argument addOne(2) #=> returns 3, as we want partialAdd = partial(add) # no args provided, but this still returns a function addOne = partialAdd(1) # oops! can only use a partially applied function once, so now we're trying to add one to an undefined value (no second argument), and we get an error 

En resumen, use la aplicación parcial para rellenar algunos valores, sabiendo que la próxima vez que llame al método, se ejecutará, dejando indefinidos todos los argumentos no provistos; utilice el currying cuando quiera devolver continuamente una función parcialmente aplicada tantas veces como sea necesario para cumplir la firma de la función. Un último ejemplo artificial:

 curriedAdd = curry(add) curriedAdd()()()()()(1)(2) # ugly and dumb, but it works partialAdd = partial(add) partialAdd()()()()()(1)(2) # second invocation of those 7 calls fires it off with undefined parameters 

¡Espero que esto ayude!

ACTUALIZACIÓN: algunas implementaciones de idiomas o lib le permitirán pasar una aridad (número total de argumentos en la evaluación final) a la implementación parcial de la aplicación que puede combinar mis dos descripciones en un lío confuso … pero en ese punto, las dos técnicas son en gran medida intercambiable.

Currying es una función de un argumento que toma una función f y devuelve una nueva función h :

 curry(f) = h 

La aplicación parcial es una función de dos (o más) argumentos que toma una función f uno o más argumentos adicionales para f y devuelve una nueva función g :

 part(f, 2) = g 

La confusión surge porque con una función de dos argumentos se cumple la siguiente igualdad:

 partial(f, a) = curry(f)(a) 

Ambos lados producirán la misma función de argumento único.

La igualdad no es verdadera para funciones de aria superior porque en este caso currying devolverá una función de argumento único, mientras que la aplicación parcial devolverá una función de argumento múltiple.

La diferencia también está en el comportamiento, mientras que currying transforma toda la función original recursivamente (una vez para cada argumento), la aplicación parcial es solo un reemplazo de un paso.

Fuente: Wikipedia Currying .

Aquí hay otras excelentes respuestas, pero creo que este ejemplo (según mi entendimiento) en Java podría ser beneficioso para algunas personas:

 public static  Function< B, X > partiallyApply( BiFunction< A, B, X > aBiFunction, A aValue ){ return b -> aBiFunction.apply( aValue, b ); } public static  Supplier< X > partiallyApply( Function< A, X > aFunction, A aValue ){ return () -> aFunction.apply( aValue ); } public static  Function< A, Function< B, X > > curry( BiFunction< A, B, X > bif ){ return a -> partiallyApply( bif, a ); } 

Así que currying le da una función de un argumento para crear funciones, donde la aplicación parcial crea una función de envoltura que codifica uno o más argumentos.

Si desea copiar y pegar, la siguiente es más ruidosa pero más amigable para trabajar, ya que los tipos son más indulgentes:

 public static  Function< ? super B, ? extends X > partiallyApply( final BiFunction< ? super A, ? super B, X > aBiFunction, final A aValue ){ return b -> aBiFunction.apply( aValue, b ); } public static  Supplier< ? extends X > partiallyApply( final Function< ? super A, X > aFunction, final A aValue ){ return () -> aFunction.apply( aValue ); } public static  Function< ? super A, Function< ? super B, ? extends X > > curry( final BiFunction< ? super A, ? super B, ? extends X > bif ){ return a -> partiallyApply( bif, a ); } 

Al escribir esto, confundí currying y uncurrying. Son transformaciones inversas en las funciones. Realmente no importa lo que tú llames, siempre y cuando obtengas lo que representan la transformación y su inversa.

No se define de manera muy clara la falta de recorrido (o más bien, hay definiciones “contradictorias” que capturan el espíritu de la idea). Básicamente, significa convertir una función que toma múltiples argumentos en una función que toma un solo argumento. Por ejemplo,

 (+) :: Int -> Int -> Int 

Ahora, ¿cómo convertir esto en una función que toma un solo argumento? ¡Haces trampa, por supuesto!

 plus :: (Int, Int) -> Int 

Observe que más ahora toma un solo argumento (que se compone de dos cosas). ¡Súper!

¿Cuál es el punto de esto? Bien, si tiene una función que toma dos argumentos y tiene un par de argumentos, es bueno saber que puede aplicar la función a los argumentos y obtener lo que espera. Y, de hecho, la plomería para hacerlo ya existe, por lo que no tiene que hacer cosas como la coincidencia explícita de patrones. Todo lo que tienes que hacer es:

 (uncurry (+)) (1,2) 

Entonces, ¿qué es la aplicación de función parcial? Es una forma diferente de convertir una función en dos argumentos en una función con un argumento. Sin embargo, funciona de manera diferente. De nuevo, tomemos (+) como ejemplo. ¿Cómo podríamos convertirlo en una función que toma un único Int como argumento? ¡Hacemos trampa!

 ((+) 0) :: Int -> Int 

Esa es la función que agrega cero a cualquier Int.

 ((+) 1) :: Int -> Int 

agrega 1 a cualquier Int. Etc. En cada uno de estos casos, (+) es “parcialmente aplicado”.

Respuesta simple

Curry: le permite llamar a una función, dividiéndola en varias llamadas, proporcionando un argumento por llamada.

Parcial: le permite llamar a una función, dividiéndola en varias llamadas, proporcionando múltiples argumentos por llamada.


Consejos simples

Ambos le permiten llamar a una función que proporciona menos argumentos (o, mejor, proporcionándoles acumulativamente). En realidad, ambos vinculan (en cada llamada) un valor específico a los argumentos específicos de la función.

La diferencia real se puede ver cuando la función tiene más de 2 argumentos.


Simple e (c) (muestra)

(en Javascript)

 function process(context, success_callback, error_callback, subject) {...} 

¿Por qué siempre pasar los argumentos, como el contexto y las devoluciones de llamada, si siempre serán los mismos? Solo une algunos valores para la función

 processSubject = _.partial(process, my_context, my_success, my_error) 

y llámalo en subject1 y foobar con

 processSubject('subject1'); processSubject('foobar'); 

Cómodo, ¿no? 😉

Con currying , necesitarías pasar un argumento por tiempo

 curriedProcess = _.curry(process); processWithBoundedContext = curriedProcess(my_context); processWithCallbacks = processWithBoundedContext(my_success)(my_error); // note: these are two sequential calls result1 = processWithCallbacks('subject1'); // same as: process(my_context, my_success, my_error, 'subject1'); result2 = processWithCallbacks('foobar'); // same as: process(my_context, my_success, my_error, 'foobar'); 

Renuncia

Me salté toda la explicación académica / matemática. Porque yo no lo sé Tal vez ayudó 🙃