Pregunta

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?

¿Fue útil?

Solución

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

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

Cuando se 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), debes hacer esto:

f(x)(y)(z);

Muchos lenguajes funcionales le permiten escribir f x y z . Si solo llama a fxy o f (x) (y) , obtiene una función parcialmente aplicada: el valor de retorno es un cierre de lambda (z) {z (x (y))} con los valores de x e y pasados ??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

Otros consejos

La forma más fácil de ver en qué se diferencian 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:

  • Aplicación parcial la función Add con un valor 7 nos dará una nueva función como salida. Esa función en sí misma 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
    
  • Currying la función Add nos dará una nueva función como salida. Esa función toma 1 número como entrada y genera todavía 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, " currying " y " aplicación parcial " Son dos funciones totalmente diferentes. El curry 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 tienen formas totalmente diferentes, como se demostró anteriormente.

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

  

Curry significa dividir una función con muchos argumentos en una serie   de funciones que cada uno toma un argumento y finalmente produce el   mismo resultado que la función original. El curry es probablemente el más   tema desafiante para los desarrolladores nuevos en la programación funcional, particularmente porque   A menudo se confunde con la aplicación parcial. Puedes ver ambas 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   lenguas imperativas. La segunda declaración crea una nueva función.   llamado doble 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 hubiera 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 la multiplicación se hace para formar el doble.   Pero esto es solo algo cierto. La función de multiplicar está al curry, pero   eso sucede cuando se define porque las funciones en F # se cursan por   defecto. Cuando se crea la doble función, es más preciso   digamos que la función de multiplicación se aplica parcialmente.

     

La función de multiplicación es realmente una serie de dos funciones. El primero   la 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   llamando 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 del doble.

     

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

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

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

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

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 utilizando solo funciones unarias (es decir, cada función es unaria). En la práctica y como un subproducto, es una técnica que puede hacer que muchas aplicaciones funcionales parciales útiles (pero no todas) sean triviales, si el lenguaje tiene funciones de 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 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.

Curry es una función del argumento one que toma una función f y devuelve una nueva función h . Tenga en cuenta que h toma un argumento de X y devuelve una función que asigna Y a 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 toman una función f y uno o más argumentos adicionales para f y 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 .

La diferencia entre curry y 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 menor aridad; en el ejemplo anterior, f tiene una aridad de 3, mientras que parcial solo tiene una aridad de 2. Lo más importante es que una función parcialmente aplicada devolvería el resultado de inmediato. al ser invocado , no hay otra función en la cadena de curry. Entonces, si está viendo algo como partial (2) (3) , en realidad no es una aplicación parcial.

Lectura adicional:

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 curry devolviendo un cierre: no se evalúa bajo lambda cuando se aplica parcialmente. Por lo tanto, para que la aplicación parcial sea interesante, debemos hacer una diferencia entre el curry 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ática teórica o programación funcional, pero a partir de mi breve incursión en FP, parece que el 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 variad con un número indeterminado de argumentos. Sé que algunos de los ejemplos de 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

Este es obviamente 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 currículum 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.

Una vez más, 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 directo inmediato. Además, si el CoffeeScript es difícil de leer, visite coffeescript.org, haga clic en " intente en coffeescript " y péguelo en mi código para ver la versión compilada, que puede (con suerte) tener más sentido. Gracias!

Tenía esta pregunta mucho mientras aprendía y desde entonces me la han hecho muchas veces. La forma más sencilla en que puedo describir la diferencia es que ambas son iguales :) Permítame explicarle ... 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 un " addOne " Función, podría aplicarlo parcialmente o curry:

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

Ahora usarlos está claro:

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

Entonces, ¿cuál es la diferencia? Bueno, es sutil, pero la aplicación parcial implica proporcionar algunos argumentos y la función devuelta 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 previamente algunos valores, sabiendo que la próxima vez que llame al método, se ejecutará, dejando indefinidos todos los argumentos sin proporcionar; 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 de 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, el dos técnicas son en gran parte intercambiables.

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.


Sugerencias simples

Ambos le permiten llamar a una función proporcionando menos argumentos (o, mejor, proporcionándolos de forma acumulativa). En realidad, ambos vinculan (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.


e (c) simple (muestra)

(en Javascript)

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

¿por qué siempre se pasan los argumentos, como el contexto y las devoluciones de llamada, si siempre serán iguales? Simplemente vincule algunos valores para la función

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

y llámelo en subject1 y foobar con

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

Cómodo, ¿no? & # 128521;

Con curry tendrías que 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ó a & # 128579;

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

Entonces el currículum le 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.

Si desea copiar & amp; 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 );
}

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

La no corriente no está definida muy claramente (o, mejor dicho, hay "definiciones en conflicto" 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? ¡Haces trampa, por supuesto!

plus :: (Int, Int) -> Int

Observe 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. De nuevo, 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".

Voy a 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 puedas usar los conceptos completamente, pero los entiendes 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.

Currying es cuando define la función.

Aplicación parcial es cuando llama la función.

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.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top