web-dev-qa-db-fra.com

Quelle est la différence entre un currying et une application partielle?

Je vois assez souvent sur Internet diverses plaintes selon lesquelles les exemples de curry d'autres peuples ne le sont pas, mais ne sont en réalité qu'une application partielle.

Je n'ai pas trouvé d'explication valable sur ce qu'est une application partielle ou sur sa différence avec le curry. Il semble y avoir une confusion générale, des exemples équivalents étant décrits comme currying dans certains endroits et une application partielle dans d’autres.

Quelqu'un pourrait-il me fournir une définition des deux termes et des précisions sur leur différence?

404
SpoonMeiser

Le currying consiste à convertir une seule fonction de n arguments en n fonctions avec seul argument chacun. Étant donné la fonction suivante:

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

Au curry, devient:

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

Pour obtenir l’application complète de f (x, y, z), vous devez procéder comme suit:

f(x)(y)(z);

De nombreux langages fonctionnels vous permettent d'écrire f x y z. Si vous appelez seulement f x y Ou f (x) (y) , vous obtenez une fonction partiellement appliquée: la valeur renvoyée est un fermeture de lambda(z){z(x(y))} avec passé les valeurs de x et y à f(x,y).

Une façon d'utiliser une application partielle consiste à définir des fonctions en tant qu'applications partielles de fonctions généralisées, comme 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
234
Mark Cidade

Le moyen le plus simple de voir en quoi elles diffèrent est de considérer un exemple réel. Supposons que nous ayons une fonction Add qui prend 2 nombres en entrée et retourne un nombre en sortie, par ex. Add(7, 5) retourne 12. Dans ce cas:

  • Application partielle la fonction Add avec une valeur 7 nous donnera une nouvelle fonction en sortie. Cette fonction elle-même prend 1 chiffre en entrée et génère un nombre. En tant que tel:

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

    Donc on peut faire ça:

    f2 = Partial(Add, 7);
    f2(5); // returns 12;
           // f2(7)(5) is just a syntactic shortcut
    
  • Currying la fonction Add nous donnera une nouvelle fonction en sortie. Cette fonction elle-même prend 1 nombre en entrée et génère encore une autre nouvelle fonction. Cette troisième fonction prend alors 1 nombre en entrée et renvoie un nombre en sortie. En tant que tel:

    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
    

    Donc on peut faire ça:

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

En d'autres termes, "currying" et "application partielle" sont deux fonctions totalement différentes. Le curry prend exactement 1 entrée, alors que l'application partielle en prend 2 (ou plus).

Même si les deux renvoient une fonction en sortie, les fonctions renvoyées ont des formes totalement différentes, comme indiqué ci-dessus.

151
Pacerier

Remarque: ceci provient de F # Basics , un excellent article d'introduction pour les développeurs .NET qui se lancent dans la programmation fonctionnelle.

Currying signifie décomposer une fonction avec de nombreux arguments en une série de fonctions qui prennent chacune un argument et produisent finalement le même résultat que la fonction d'origine. Le currying est probablement le sujet le plus difficile pour les développeurs novices en programmation fonctionnelle, en particulier parce qu’il est souvent confondu avec une application partielle. Vous pouvez voir les deux au travail dans cet exemple:

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

Tout de suite, vous devriez voir un comportement différent de la plupart des langages impératifs. La deuxième instruction crée une nouvelle fonction appelée double en passant un argument à une fonction qui en prend deux. Le résultat est une fonction qui accepte un argument int et produit le même résultat que si vous aviez appelé multiplier avec x égal à 2 et y égal à cet argument. En termes de comportement, il est identique à ce code:

let double2 z = multiply 2 z

Souvent, les gens disent à tort que multiplie est un curry pour former un double. Mais ce n'est qu'un peu vrai. La fonction multiply est curry, mais cela arrive quand elle est définie car les fonctions en F # sont curry par défaut. Lorsque la fonction double est créée, il est plus précis de dire que la fonction multiply est partiellement appliquée.

La fonction multiplie est en réalité une série de deux fonctions. La première fonction prend un argument int et retourne une autre fonction, liant effectivement x à une valeur spécifique. Cette fonction accepte également un argument int que vous pouvez considérer comme la valeur à lier à y. Après avoir appelé cette deuxième fonction, x et y sont liés, le résultat est donc le produit de x et y tel que défini dans le corps de double.

Pour créer double, la première fonction de la chaîne de fonctions multiply est évaluée pour appliquer partiellement multiply. La fonction résultante reçoit le nom double. Lorsque double est évalué, il utilise son argument avec la valeur partiellement appliquée pour créer le résultat.

48
dodgy_coder

Question interessante. Après un peu de recherche, "L'application partielle de fonction n'est pas currying" a donné la meilleure explication que j'ai trouvée. Je ne peux pas dire que la différence pratique m'est particulièrement évidente, mais je ne suis pas un FP expert...

Une autre page utile (qui, j’avoue que je n’ai pas encore tout lu) est "Application partielle et curry avec Java Closures" .

Cela ressemble à une paire de termes très confus, remarquez.

29
Jon Skeet

J'ai répondu à cela dans un autre fil https://stackoverflow.com/a/12846865/1685865 . En bref, l’application de fonctions partielles consiste à corriger certains arguments d’une fonction multivariable donnée pour obtenir une autre fonction comportant moins d’arguments, tandis que Currying consiste à transformer une fonction de N arguments en une fonction unaire qui renvoie une fonction unaire ... Le currying est montré à la fin de ce post.]

Le curry présente un intérêt principalement théorique: on peut exprimer des calculs en utilisant uniquement des fonctions unaires (c'est-à-dire chaque fonction est unaire). En pratique et en tant que sous-produit, il s'agit d'une technique qui peut rendre triviales de nombreuses applications fonctionnelles partielles utiles (mais pas toutes), si le langage comporte des fonctions curryings. Encore une fois, ce n’est pas le seul moyen de mettre en œuvre des applications partielles. Vous pouvez donc rencontrer des scénarios dans lesquels une application partielle est effectuée d'une autre manière, mais les gens le prennent pour du Currying.

(Exemple de currying)

En pratique, on n'écrirait pas simplement

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

ou l'équivalent javascript

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

au lieu de

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

pour le currying.

13
Ji Han

Le currying est une fonction de un argument qui prend une fonction f et retourne une nouvelle fonction h. Notez que h prend un argument de X et retourne un function qui mappe Y à Z:

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

L’application partielle est une fonction de deux (ou plus) arguments prenant une fonction f et un ou plusieurs arguments supplémentaires dans f et retourne une nouvelle fonction g:

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

La confusion survient car avec une fonction à deux arguments, l’égalité suivante est vraie:

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

Les deux côtés donneront la même fonction à un argument.

L'égalité n'est pas vraie pour les fonctions d'arité supérieure, car dans ce cas, currying renverra une fonction à un argument, alors que l'application partielle renverra une fonction à arguments multiples.

La différence réside également dans le comportement, alors que currying transforme de manière récursive (une fois pour chaque argument) la fonction originale dans son ensemble, une application partielle n’est qu’un remplacement en une étape.

Source: Wikipédia Currying .

6
Roland

La différence entre l'application curry et l'application partielle peut être mieux illustrée à l'aide de l'exemple JavaScript suivant:

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

var partial = f.bind(null, 1);

6 === partial(2, 3);

Une application partielle résulte en une fonction d'arité plus petite; dans l'exemple ci-dessus, f a une arité de 3 alors que partial n'a qu'une arité de 2. Plus important encore, une fonction partiellement appliquée renverrait le résultat immédiatement après avoir été appelé , pas une autre fonction dans la chaîne de currying. Donc, si vous voyez quelque chose comme partial(2)(3), ce n'est pas une application partielle dans la réalité.

Lectures complémentaires:

5
gsklee

Réponse simple

Curry: vous permet d'appeler une fonction, en la divisant en plusieurs appels, en fournissant un argument par appel.

Partial: vous permet d'appeler une fonction en la divisant en plusieurs appels et en fournissant plusieurs arguments par appel.


Astuces simples

Les deux vous permettent d'appeler une fonction fournissant moins d'arguments (ou, mieux, en les fournissant de manière cumulative). En réalité, les deux associent (à chaque appel) une valeur spécifique à des arguments spécifiques de la fonction.

La vraie différence peut être vue lorsque la fonction a plus de 2 arguments.


Simple e (c) (échantillon)

(en Javascript)

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

pourquoi toujours passer les arguments, comme le contexte et les rappels, s'ils seront toujours les mêmes? Il suffit de lier quelques valeurs pour la fonction

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

et appelez-le sur subject1 et foobar avec

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

Confortable, n'est ce pas? ????

Avec currying , vous devez passer un argument à la fois.

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

Avertissement

J'ai sauté toutes les explications académiques/mathématiques. Parce que je ne le sais pas. Peut-être que ça a aidé ????

2
Kamafeather

Je pourrais très bien me tromper ici, car je n’ai pas de connaissances approfondies en mathématiques théoriques ou en programmation fonctionnelle, mais de ma brève incursion en FP, il semble que currying ait tendance à transformer une fonction de N arguments en N fonctions d’un argument, tandis que l'application partielle [en pratique] fonctionne mieux avec des fonctions variadiques avec un nombre indéterminé d'arguments. Je sais que certains des exemples des réponses précédentes défient cette explication, mais cela m’a le plus aidé à séparer les concepts. Considérez cet exemple (écrit en CoffeeScript pour plus de concision, mes excuses si cela gêne davantage, mais veuillez demander des éclaircissements, si nécessaire):

# 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

Ceci est évidemment un exemple artificiel, mais notez que l'application partielle d'une fonction qui accepte un nombre quelconque d'arguments nous permet d'exécuter une fonction mais avec quelques données préliminaires. Currying une fonction est similaire mais nous permet d’exécuter une fonction de paramètre N par morceaux jusqu’à ce que tous les N paramètres soient pris en compte, mais seulement jusqu’à ce qu’ils le soient.

Encore une fois, voici ce que je retiens de ce que j'ai lu. Si quelqu'un n'est pas d'accord, j'apprécierais un commentaire expliquant pourquoi plutôt qu'un vote négatif immédiat. De plus, si CoffeeScript est difficile à lire, veuillez visiter coffeescript.org, cliquez sur "try coffeescript" et collez-le dans mon code pour voir la version compilée, ce qui pourrait (espérons-le) avoir plus de sens. Merci!

2
sunny-mittal

J'ai souvent eu cette question lors de mon apprentissage et depuis, je l'ai posée à plusieurs reprises. La manière la plus simple de décrire la différence est que les deux sont identiques :) Laissez-moi vous expliquer ... il y a évidemment des différences.

L'application partielle et le currying impliquent de fournir des arguments à une fonction, peut-être pas tous en même temps. Un exemple assez canonique consiste à ajouter deux nombres. En pseudocode (en réalité JS sans mot-clé), la fonction de base peut être la suivante:

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

Si je voulais une fonction "addOne", je pouvais l'appliquer partiellement ou la curry:

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

Maintenant, les utiliser est clair:

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

Alors quelle est la différence? Eh bien, c’est subtile, mais une application partielle implique la fourniture de quelques arguments et la fonction retournée sera alors exécutera la fonction principale lors de la prochaine invocation alors que le curry continuera d’attendre jusqu’à ce qu’il dispose de tous les arguments nécessaires:

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 bref, utilisez une application partielle pour pré-remplir certaines valeurs, sachant que la prochaine fois que vous appelez la méthode, celle-ci sera exécutée, laissant tous les arguments non définis non définis; utilisez currying lorsque vous souhaitez renvoyer en permanence une fonction partiellement appliquée autant de fois que nécessaire pour remplir la signature de la fonction. Un dernier exemple artificiel:

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

J'espère que cela t'aides!

UPDATE: Certaines langues ou implémentations lib vous permettront de passer une arité (nombre total d’arguments dans l’évaluation finale) à la mise en œuvre partielle de l’application, ce qui pourrait confondre mes deux descriptions dans un désordre déroutant ... mais à ce stade, les deux techniques sont: largement interchangeable.

2
sunny-mittal

Pour moi, les applications partielles doivent créer une nouvelle fonction dans laquelle les arguments utilisés sont complètement intégrés à la fonction résultante.

La plupart des langages fonctionnels implémentent le curry en renvoyant une fermeture: n'évaluez pas sous lambda lorsqu'il est partiellement appliqué. Donc, pour que l'application partielle soit intéressante, nous devons faire la différence entre currying et application partielle et considérer l'application partielle comme currying plus évaluation sous lambda.

2

Il existe d’autres bonnes réponses ici, mais je crois que cet exemple (selon ma compréhension) dans Java pourrait être avantageux pour certaines personnes:

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

Donc currying vous donne une fonction à un argument pour créer des fonctions, où partial-application crée une fonction wrapper qui code en dur un ou plusieurs arguments.

Si vous voulez copier et coller, voici ce qui est plus bruyant mais plus convivial, car les types sont plus cléments:

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
Tobogganski

En écrivant cela, j'ai confondu currying et currying. Ce sont des transformations inverses sur les fonctions. Peu importe ce que vous appelez, tant que vous obtenez ce que la transformation et son inverse représentent.

Le manque de temps n'est pas défini très clairement (ou plutôt, il existe des définitions "contradictoires" qui capturent toutes l'esprit de l'idée). Fondamentalement, cela signifie transformer une fonction prenant plusieurs arguments en une fonction prenant un seul argument. Par exemple,

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

Maintenant, comment transformez-vous cela en une fonction qui prend un seul argument? Tu triches, bien sûr!

plus :: (Int, Int) -> Int

Notez que plus prend maintenant un seul argument (composé de deux choses). Super!

Quel est le but de cela? Eh bien, si vous avez une fonction qui prend deux arguments, et vous avez une paire d'arguments, il est bon de savoir que vous pouvez appliquer la fonction aux arguments, tout en obtenant ce que vous attendez. Et, en fait, la tuyauterie nécessaire pour le faire existe déjà, de sorte que vous n’ayez pas à faire des choses comme une correspondance explicite de modèle. Tout ce que tu dois faire est:

(uncurry (+)) (1,2)

Alors, quelle est l'application de fonction partielle? C'est une façon différente de transformer une fonction à deux arguments en une fonction à un argument. Cela fonctionne différemment si. Encore une fois, prenons (+) à titre d'exemple. Comment pourrions-nous en faire une fonction prenant un seul Int comme argument? Nous trichons!

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

C'est la fonction qui ajoute zéro à n'importe quel Int.

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

ajoute 1 à n'importe quel Int. Etc. Dans chacun de ces cas, le (+) est "partiellement appliqué".

0
nomen

Je vais supposer que la plupart des gens qui posent cette question connaissent déjà les concepts de base et qu’ils n’ont donc pas besoin de parler de cela. C'est le chevauchement qui crée la confusion.

Vous pourrez peut-être utiliser pleinement les concepts, mais vous les comprenez ensemble comme ce flou conceptuel amorphe pseudo-atomique. Ce qui manque, c'est de savoir où est la limite entre eux.

Au lieu de définir chacun d'eux, il est plus facile de souligner uniquement leurs différences: la limite.

Le currying est quand vous définissez la fonction.

Application partielle est quand vous appelez la fonction.

Application est mathématique pour appeler une fonction.

Partielle l'application nécessite d'appeler une fonction au curry et d'obtenir une fonction comme type de retour.

0
Brennan Cheung