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


Muy a menudo veo en Internet varias quejas de que otros pueblos ejemplos de currying no son currying, sino que en realidad son solo una aplicación parcial.

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

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

Author: ArtB, 2008-10-20

13 answers

Currying es convertir una sola función de n argumentos en n funciones con un solo argumento cada una. Dada la siguiente función:

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

Cuando se hace 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 te permiten escribir f x y z. Si solo llamas a f x y o f(x) (y) entonces obtienes una función parcialmente aplicada-el valor devuelto es un cierre de lambda(z){z(x(y))} con pasa-en los valores de x e 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
 213
Author: Mark Cidade,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-20 11:25: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:

  • Parcial aplicando la función Add con un valor 7 nos dará una nueva función como salida. Esa función en sí toma 1 número como entrada y produce 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
    

    Así que podemos hacer esto:

    f2 = Partial(Add, 7);
    f2(5); // returns 12;
           // f2(7)(5) is just a syntactic shortcut
    
  • Currying la función Add nos dará una nueva función como salida. Esa función en sí toma 1 número como entrada y salidas sin embargo otra nueva función. Esa tercera función entonces 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
                // i.e. f2(number) = f3
    
                // f3 takes 1 number as input and returns a number as output
                // i.e. f3(number) = number
    

    Así que podemos hacer esto:

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

En otras palabras, "currying" y "partial application" son dos funciones totalmente diferentes. El curtido 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.

 131
Author: Pacerier,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-22 14:35:19

Nota: esto fue tomado de F# Basics un excelente artículo introductorio para desarrolladores de.NET entrando en la programación funcional.

Currying significa dividir una función con muchos argumentos en una serie de funciones que cada uno toma un argumento y en última instancia producen la el mismo resultado que la función original. El currying es probablemente el más tema desafiante para los desarrolladores nuevos en la programación funcional, particularmente porque a menudo se confunde con parcial aplicación. Puedes ver ambos en el trabajo en este ejemplo:

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

De inmediato, debe ver un comportamiento que es diferente de la mayoría idiomas imperativos. La segunda instrucción crea una nueva función se llama 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 misma salida como si hubieras llamado multiplicar con x igual a 2 e y igual a ese argumento. En términos de comportamiento, es lo mismo que esto código:

let double2 z = multiply 2 z

A menudo, la gente dice erróneamente que multiplicar es curry para formar doble. Pero esto es solo algo cierto. La función multiplicar es curry, pero eso sucede cuando se define porque las funciones en F # se curry por predeterminado. Cuando se crea la doble función, es más preciso digamos que la función multiplicar se aplica parcialmente.

La función multiplicar es realmente una serie de dos funciones. La primera la función toma un argumento int y devuelve otra función, vinculación efectiva de x a un valor específico. Esta función también acepta un argumento int que se puede pensar como el valor para enlazar a y. Después llamando a esta segunda función, x e y están vinculados, por lo que el resultado es el producto de x e y tal como se define en el cuerpo de double.

Para crear double, la primera función en la cadena de multiplicar funciones se evalúa para aplicar parcialmente multiplicar. El resultado la función recibe el nombre double. Cuando doble es evaluado, utiliza su argumento junto con el valor parcialmente aplicado para crear el resultado.

 47
Author: dodgy_coder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-04 05:18:33

Pregunta Interesante. Después de un poco de búsqueda, "Partial Function Application is not currying" 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 completamente) es "Currying and Partial Application with Java Closures".

Parece que este es un par de términos muy confusos, eso sí.

 27
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-20 11:02:06

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

La moneda es principalmente de interés teórico: uno puede expresa cálculos usando solo funciones unarias (es decir, cada función es unaria). 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 curry. Una vez más, no es el único medio para implementar aplicaciones parciales. Así que usted podría encontrar escenarios donde la aplicación parcial se hace de otra manera, pero la gente está confundiendo como Currying.

(Ejemplo de Currying)

En la práctica uno no escribiría simplemente

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

O el equivalente javascript

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

 12
Author: Ji Han,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:47:26

La diferencia entre 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 resulta en una función de aridad menor; en el ejemplo anterior, f tiene una aridad de 3 mientras que partial solo tiene una aridad de 2. Más importante aún, una función parcialmente aplicada devolvería el resultado de inmediato al ser invocada, no otra función en la cadena de curtido. Así que si estás viendo algo como partial(2)(3), no es aplicación parcial en la actualidad.

Lectura adicional:

 5
Author: Kay,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-27 03:51:08

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 currying devolviendo un cierre: no evalúe bajo lambda cuando se aplica parcialmente. Por lo tanto, para que la aplicación parcial sea interesante, necesitamos hacer una diferencia entre la aplicación de currying y parcial y considerar la aplicación parcial como currying más evaluación bajo lambda.

 2
Author: Taoufik Dachraoui,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-13 10:03:40

Podría estar muy equivocado aquí, ya que no tengo una sólida formación en matemáticas teóricas o programación funcional, pero de 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 variádicas 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 más a separar la concepto. Considere este ejemplo (escrito en CoffeeScript para ser conciso, mis disculpas si confunde aún más, pero por favor pida 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

Este es obviamente un ejemplo artificial, pero observe que aplicar parcialmente una función que acepta cualquier número de argumentos nos permite ejecutar una función pero con algunos datos preliminares. Curtir una función es similar pero nos permite ejecutar una función de N-parámetro en piezas hasta, pero solo hasta, todos los N parámetros son contabilizado.

De nuevo, esta es mi toma de las cosas que he leído. Si alguien no está de acuerdo, agradecería un comentario sobre por qué en lugar de una votación negativa inmediata. Además, si el CoffeeScript es difícil de leer, por favor visite coffeescript.org, haga clic en "probar coffeescript" y pegue mi código para ver la versión compilada, que puede (con suerte) tener más sentido. ¡Gracias!

 2
Author: sunny-mittal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-07 05:07:07

Tuve esta pregunta mucho mientras aprendía y desde entonces se me ha hecho muchas veces. La forma más sencilla que puedo describir la diferencia es que ambos son lo mismo:) Permítanme explicar...obviamente, hay diferencias.

Tanto la aplicación parcial como el curtido implican suministrar argumentos a una función, quizás no todos a la vez. Un ejemplo bastante canónico es sumar 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 "addOne", podría aplicarla parcialmente o curry:

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

Ahora usarlas es claro:

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

Entonces, ¿cuál es la diferencia? Bueno, es sutil, pero la aplicación parcial implica suministrar algunos argumentos y la función devuelta entonces ejecutará la función principal en la siguiente 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 el siguiente cuando llame al método, se ejecutará, dejando indefinidos todos los argumentos no divididos; use currying cuando desee devolver continuamente una función parcialmente aplicada tantas veces como sea necesario para cumplir con 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: Algunos lenguajes o implementaciones lib le permitirán pasar un arity (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...pero en ese punto, las dos técnicas son en gran medida intercambiables.

 2
Author: sunny-mittal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-13 02:30:17

Alarmada es una función de una 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 y uno o más argumentos adicionales a 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 mantiene la siguiente igualdad:

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

Ambos lados producirán el mismo argumento función.

La igualdad no es cierta para las funciones de aridad superior porque en este caso el currying devolverá una función de un argumento, mientras que la aplicación parcial devolverá una función de argumentos múltiples.

La diferencia está también 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 .

 2
Author: Roland,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-10 11:24:26

Hay otras grandes respuestas aquí, pero creo que este ejemplo (según mi entendimiento) en Java podría ser de beneficio para algunas personas:

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

public static <A,X> Supplier< X > partiallyApply( Function< A, X > aFunction, A aValue ){
    return () -> aFunction.apply( aValue );
}

public static <A,B,X> 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, lo siguiente es más ruidoso pero más amigable para trabajar ya que los tipos son más indulgentes:

public static <A,B,X> 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 <A,X> Supplier< ? extends X > partiallyApply( final Function< ? super A, X > aFunction, final A aValue ){
    return () -> aFunction.apply( aValue );
}

public static <A,B,X> Function<  ? super A,  Function< ? super B, ? extends X >  > curry( final BiFunction< ? super A, ? super B, ? extends X > bif ){
    return a -> partiallyApply( bif, a );
}
 1
Author: ArtB,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-30 20:56:21

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

No se define con mucha claridad (o más bien, hay definiciones "conflictivas" 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. Para 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 plus ahora toma un solo argumento (que se compone de dos cosas). Super!

¿Qué sentido tiene esto? Bueno, si tienes una función que toma dos argumentos, y tienes un par de argumentos, es bueno saber que puedes aplicar la función a los argumentos, y aún así obtener lo que esperas. Y, de hecho, la plomería para hacerlo ya existe, para que no tengas 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. Una vez más, tomemos (+) como ejemplo. ¿Cómo podríamos convertirlo en una función que toma un solo Int como argumento? ¡Hacemos trampa!

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

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

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

Añade 1 a cualquier Int. Sucesivamente. En cada uno de estos casos, (+) se "aplica parcialmente".

 0
Author: nomen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-09 03:29:17

Respuesta simple

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

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


Consejos simples

Ambos le permiten llamar a una función que proporciona menos argumentos (o, mejor, proporcionándolos acumulativamente). En realidad, ambos enlazan (en cada llamada) un valor específico a 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 enlaza algunos valores para la función

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

Y llamada en subject1 y foobar con

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

Cómodo, ¿no?

Con currying usted necesidad de pasar un argumento por vez

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');

Descargo de responsabilidad

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

 0
Author: Kamafeather,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-10 09:56:15