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

438

A menudo veo en Internet varias quejas de que los ejemplos de curry de otras personas no son curry, sino que en realidad son solo una aplicación parcial.

No he encontrado una explicación decente de qué es una aplicación parcial o cómo difiere del curry. Parece haber una confusión general, con ejemplos equivalentes que se describen como curry 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?

SpoonMeiser
fuente

Respuestas:

256

Curry es convertir una sola función de n argumentos en n funciones con un solo 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 te permiten escribir f x y z. Si sólo llamar f x yo f (x) (y) entonces se obtiene un valor de retorno de la función parcialmente aplicada es un cierre de lambda(z){z(x(y))}la aprobada 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, accumulator, 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(accumulator,list) {/*...*/}
f(0,list) //returns 10
@g = f(0) //same as sum
g(list)  //returns 10
Mark Cidade
fuente
40
¿Estás diciendo que una aplicación parcial es cuando curreas una función y usas algunas, pero no todas las funciones resultantes?
SpoonMeiser
99
mas o menos si. Si solo proporciona un subconjunto de argumentos, obtendrá una función que acepta el resto de los argumentos
Mark Cidade,
1
¿Cambiar una función f (a, b, c, d) a g (a, b) contaría como una aplicación parcial? ¿O es solo cuando se aplica a las funciones curry? Lamento ser un dolor, pero estoy buscando una respuesta explícita aquí.
SpoonMeiser
2
@ Mark: Supongo que este es solo uno de esos conceptos que saca a relucir el pedante en mí, pero una apelación a las fuentes autorizadas hace poco para satisfacer, ya que todos parecen apuntar el uno al otro. Wikipedia no es lo que considero una fuente autorizada, pero entiendo que es difícil encontrar mucho más. ¡Baste decir que creo que los dos sabemos de lo que hablamos y el poder de los mismos, independientemente de si podemos o no estar de acuerdo (o no) en los detalles de la lengua vernácula! :) Gracias Mark!
Jason Bunting
55
@JasonBunting, Con respecto a tu primer comentario, de lo que estabas hablando es de descurrimiento . Curry es tomar una función de múltiples argumentos como entrada y devolver una cadena de funciones de 1 argumento como salida. Descurrir es tomar una cadena de funciones 1-arg como entrada y devolver una función multi-arg como salida. Tal como se
detalla
165

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

  • La aplicación parcial de la función Addcon un valor 7nos dará una nueva función como salida. Esa función en sí 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
    
  • El curry de la función Addnos dará una nueva función como salida. Esa función en sí dura 1 número como entrada y salidas sin embargo, otra función nueva. Esa tercera función 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
    

    Entonces podemos hacer esto:

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

En otras palabras, "curry" y "aplicación parcial" son dos funciones totalmente diferentes. El curry requiere exactamente 1 entrada, mientras que la aplicación parcial requiere 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.

Pacerier
fuente
25
La aplicación parcial transforma una función de n-arya (x - n)-ary, curry de n-arya n * 1-ary. Una función parcialmente aplicada tiene un alcance reducido (de aplicación), es decir, Add7es menos expresivo que Add. Una función curry, por otro lado, es tan expresiva como la función original.
bob
44
Creo que el rasgo más distintivo es cuando curry f (x, y, z) => R, obtenemos f (x) que devuelve g (y) => h (z) => R, cada uno de los cuales consume un solo argumento; pero cuando aplicamos parcialmente f (x, y, z) como f (x) obtenemos g (y, z) => R, es decir, con dos argumentos. Si no fuera por ese rasgo, podríamos decir que curry es como una aplicación parcial a 0 argumentos, dejando todos los argumentos sin consolidar; sin embargo, en realidad, f () parcialmente aplicado a 0 argumentos es una función que consume 3 args a la vez, a diferencia del curry f ().
Maksim Gumerov
2
Una vez más, la respuesta correcta no es la primera o la más votada: la explicación simple de la firma de curry vs. parcial al final de esta respuesta es realmente la forma más fácil de resolver la pregunta.
fnl
2
¿Qué significa el comentario f2(7)(5) is just a syntactic shortcut? (Sé muy poco.) ¿Todavía no f2contiene / "sabe acerca de" 7?
Zach Mierzejewski
@Pacerier, ¿hay una curryimplementación en alguna parte (no creo que esté dentro functools)
alancalvitti
51

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

Curry significa dividir una función con muchos argumentos en una serie de funciones, cada una de las cuales toma un argumento y finalmente produce el mismo resultado que la función original. El curry es probablemente el tema más desafiante para los desarrolladores nuevos en la programación funcional, particularmente porque a menudo se confunde con una aplicación parcial. 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, debería ver un comportamiento diferente al de la mayoría de los lenguajes imperativos. La segunda instrucción crea una nueva función llamada doble al pasar 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 hubiera 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 curry para formar el doble. Pero esto es solo algo cierto. La función de multiplicación es curry, pero eso sucede cuando se define porque las funciones en F # se curry por defecto. Cuando se crea la función doble, es más preciso decir que la función de multiplicación se aplica parcialmente.

La función de multiplicación 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 puedes considerar como el valor para enlazar a y. Después de llamar a esta segunda función, x e y están unidos, por lo que el resultado es el producto de x e y como se define en el cuerpo de double.

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

dodgy_coder
fuente
33

Interesante pregunta. Después de un poco de búsqueda, "La aplicación de función parcial no curry" dio la mejor explicación que encontré. No puedo decir que la diferencia práctica sea ​​particularmente obvia para mí, pero no soy un experto en FP ...

Otra página de aspecto útil (que confieso que aún no he leído completamente) es "Aplicación de currículum y parcial con cierres de Java" .

Parece que este es un par de términos ampliamente confundido, eso sí.

Jon Skeet
fuente
55
El primer enlace es perfecto sobre las diferencias. Aquí hay otro que encontré útil: bit.ly/CurryingVersusPartialApplication
Jason Bunting
55
El curry tiene que ver con las tuplas (convertir una función que toma un argumento de tupla en una que toma n argumentos separados, y viceversa). La aplicación parcial es la capacidad de aplicar una función a algunos argumentos, produciendo una nueva función para los argumentos restantes. Es fácil de recordar si solo piensas curry == que ver con tuplas.
Don Stewart
99
Los enlaces de @Jon que publicó son informativos, pero será mejor ampliar su respuesta y agregar más información aquí.
Zaheer Ahmed
11
No puedo creer que haya recibido 20 votos a favor por un par de enlaces y una admisión que realmente no conoce la diferencia entre el curry y la aplicación parcial. Bien jugado, señor.
AlienWebguy
16

He respondido esto en otro hilo https://stackoverflow.com/a/12846865/1685865 . En resumen, la aplicación de función parcial se trata de arreglar 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 unaria ... [Un ejemplo de El curry se muestra al final de esta publicación.]

El curry es principalmente de interés teórico: uno puede expresar 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. Nuevamente, 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 la gente lo confunde con Curry.

(Ejemplo de curry)

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 vez de

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

por el bien de Curry.

Ji Han
fuente
1
¿Diría que el curry es un caso específico de aplicación parcial entonces?
SpoonMeiser
1
@SpoonMeiser, No, curry no es un caso específico de aplicación parcial: una aplicación parcial de una función de 2 entradas no es lo mismo que curry la función. Ver stackoverflow.com/a/23438430/632951 .
Pacerier
10

El curry es una función de un argumento que toma una función fy devuelve una nueva función h. Tenga en cuenta que htoma un argumento de Xy devuelve una función que se asigna Ya Z:

curry(f) = h 
f: (X x Y) -> Z 
h: X -> (Y -> Z)

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

part(f, 2) = g
f: (X x Y) -> Z 
g: Y -> Z

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

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

Ambas partes producirán la misma función de un argumento.

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

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

Fuente: Wikipedia Currying .

Roland
fuente
8

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 más pequeña; en el ejemplo anterior, ftiene una aridad de 3 mientras que partialsolo tiene una aridad de 2. Más importante aún, una función parcialmente aplicada devolvería el resultado de inmediato al invocarla , no otra función en la cadena de curry. Entonces, si está viendo algo así partial(2)(3), no es una aplicación parcial en la actualidad.

Otras lecturas:

gsklee
fuente
"una función parcialmente aplicada devolvería el resultado de inmediato al ser invocada", eso no es correcto, ¿verdad? cuando aplico parcialmente una función, esa expresión devuelve una función, no "un resultado". Ok, probablemente quisiste decir que esta última función, cuando se llama con los argumentos restantes, devuelve el resultado, a diferencia de excavar un paso hacia el curry. Pero nadie dice realmente que tiene que especificar todos los argumentos restantes: puede aplicar parcialmente el resultado de una aplicación parcial, y eso será una vez más una función, no un "resultado"
Maksim Gumerov
6

Respuesta simple

Curry: le permite llamar a una función, dividirla 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, que los proporciona de forma acumulativa). En realidad, ambos vinculan (en cada llamada) un valor específico a argumentos específicos de la función.

La verdadera diferencia 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é pasar siempre 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 llamarlo sobre subject1 y foobar con

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

Cómodo, ¿no? 😉

Con el curry necesitarías 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ó 🙃

Kamafeather
fuente
4

Tuve esta pregunta mucho mientras aprendía y desde entonces me la han hecho muchas veces. La forma más simple de describir la diferencia es que ambos son iguales :) Permítanme explicar ... obviamente hay diferencias.

Tanto la aplicación parcial como el currículum implican proporcionar 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 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 curry 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 una 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 proporcionados; use el curry 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 confuso ... pero en ese punto, las dos técnicas son En gran parte intercambiable.

soleado-mittal
fuente
3

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

La mayoría de los lenguajes funcionales implementan el curry devolviendo un cierre: no evalúe bajo lambda cuando se aplica parcialmente. Entonces, para que la aplicación parcial sea interesante, necesitamos hacer una diferencia entre el curry y la aplicación parcial y considerar la aplicación parcial como curry más evaluación bajo lambda.

Taoufik Dachraoui
fuente
3

Podría estar muy equivocado aquí, ya que no tengo una sólida formación en matemática teórica o programación funcional, pero desde mi breve incursión en FP, parece que el curry 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 más a separar los conceptos. Considere este ejemplo (escrito en CoffeeScript para ser breve, mis disculpas si se 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 artificial, pero tenga en cuenta 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. El curry de una función es similar, pero nos permite ejecutar una función de N parámetros en partes hasta, pero solo hasta que, todos los N parámetros se tengan en cuenta.

Nuevamente, 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 un voto negativo inmediato. Además, si el CoffeeScript es difícil de leer, 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!

soleado-mittal
fuente
2

Asumiré que la mayoría de las personas que hacen esta pregunta ya están familiarizadas con los conceptos básicos, por lo que no es necesario hablar de eso. La superposición es la parte confusa.

Es posible que pueda utilizar los conceptos por completo, pero los entiende juntos como este desenfoque conceptual amorfo pseudoatómico. Lo que falta es saber dónde está el límite entre ellos.

En lugar de definir qué es cada uno, es más fácil resaltar solo sus diferencias: el límite.

Curry es cuando define la función.

Aplicación parcial es cuando llamas a la función.

La aplicación es matemática para llamar a una función.

La aplicación parcial requiere llamar a una función currificada y obtener una función como tipo de retorno.

Brennan Cheung
fuente
1

Hay otras excelentes respuestas aquí, pero creo que este ejemplo (según mi entendimiento) en Java podría ser beneficioso 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 );
}

Por lo tanto, el currículum le brinda una función de un argumento para crear funciones, donde la aplicación parcial crea una función de contenedor 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 );
}
Trineo
fuente
Lo siguiente me dio una idea clave: "Entonces el currículum te da una función de un argumento para crear funciones, donde la aplicación parcial crea una función envolvente que codifica uno o más argumentos".
Roland
0

Al escribir esto, confundí curry y no curry. Son transformaciones inversas en funciones. Realmente no importa cómo llames, siempre y cuando obtengas lo que representa la transformación y su inverso.

La falta de prisa no se define muy claramente (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. Por ejemplo,

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

Ahora, ¿cómo convierte esto en una función que toma un solo argumento? Usted hace trampa, por supuesto!

plus :: (Int, Int) -> Int

Tenga en cuenta que plus ahora toma un solo argumento (que se compone de dos cosas). ¡Súper!

¿Cuál es el punto de esto? Bueno, 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 aún así 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. Nuevamente, tomemos (+) como ejemplo. ¿Cómo podríamos convertirlo en una función que tome un solo 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, (+) se "aplica parcialmente".

no hombre
fuente