Est-ce que groovy appelle une application partielle «currying»?

15

Groovy a un concept qu'il appelle «curry». Voici un exemple de leur wiki:

def divide = { a, b -> a / b }

def halver = divide.rcurry(2)

assert halver(8) == 4

Ma compréhension de ce qui se passe ici est que l'argument de droite de divideest lié à la valeur 2. Cela ressemble à une forme d'application partielle.

Le terme currying est généralement utilisé pour désigner la transformation d'une fonction qui prend une série d'arguments en une fonction qui ne prend qu'un seul argument et renvoie une autre fonction. Par exemple, voici le type de la curryfonction dans Haskell:

curry :: ((a, b) -> c) -> (a -> (b -> c))

Pour les personnes qui ne sont pas utilisés Haskell a, bet csont tous les paramètres génériques. curryprend une fonction avec deux arguments et renvoie une fonction qui prend aet retourne une fonction de bà c. J'ai ajouté une paire supplémentaire de supports au type pour le rendre plus clair.

Ai-je mal compris ce qui se passe dans l'exemple groovy ou s'agit-il simplement d'une application partielle mal nommée? Ou fait-il en fait les deux: c'est-à-dire se convertir divideen fonction curry puis s'appliquer partiellement 2à cette nouvelle fonction.

Richard Warburton
la source
1
pour ceux qui ne parlent pas haskell msmvps.com/blogs/jon_skeet/archive/2012/01/30/…
jk.

Réponses:

14

La mise en œuvre de Groovy curryne fait à aucun moment curry, même en coulisses. Elle est essentiellement identique à une application partielle.

Les curry, rcurryet les ncurryméthodes renvoient un CurriedClosureobjet qui contient les arguments liés. Il a également une méthode getUncurriedArguments(mal nommée - vous utilisez des fonctions, pas des arguments) qui retourne la composition des arguments qui lui sont passés avec les arguments liés.

Lorsqu'une fermeture est appelée, elle appelle finalement la invokeMethodméthode deMetaClassImpl , qui vérifie explicitement si l'objet appelant est une instance de CurriedClosure. Si c'est le cas, il utilise ce qui précède getUncurriedArgumentspour composer le tableau complet des arguments à appliquer:

if (objectClass == CurriedClosure.class) {
    // ...
    final Object[] curriedArguments = cc.getUncurriedArguments(arguments);
    // [Ed: Yes, you read that right, curried = uncurried. :) ]
    // ...
    return ownerMetaClass.invokeMethod(owner, methodName, curriedArguments);
}

Sur la base de la nomenclature déroutante et quelque peu incohérente ci-dessus, je soupçonne que celui qui a écrit cela a une bonne compréhension conceptuelle, mais a peut-être été un peu précipité et, comme beaucoup de gens intelligents, a confondu le curry avec une application partielle. C'est compréhensible (voir la réponse de Paul King), même si c'est un peu malheureux; il sera difficile de corriger cela sans rompre la rétrocompatibilité.

Une solution que j'ai suggérée est de surcharger la curryméthode de telle sorte que quand aucun argument n'est passé, elle fasse un curry réel et déconseille d'appeler la méthode avec des arguments en faveur d'une nouvelle partialfonction. Cela peut sembler un peu étrange , mais cela maximiserait la compatibilité descendante - car il n'y a aucune raison d'utiliser une application partielle avec zéro argument - tout en évitant la situation (IMHO) plus laide d'avoir une nouvelle fonction, différemment nommée, pour un curry approprié alors que la fonction en fait nommé curryfait quelque chose de différent et de similaire.

Il va sans dire que le résultat de l'appel curryest complètement différent du curry réel. Si cela fonctionnait vraiment, vous pourriez écrire:

def add = { x, y -> x + y }
def addCurried = add.curry()   // should work like { x -> { y -> x + y } }
def add1 = addCurried(1)       // should work like { y -> 1 + y }
assert add1(1) == 2 

… Et ça marcherait, parce que addCurriedça devrait marcher comme ça { x -> { y -> x + y } }. Au lieu de cela, il lance une exception d'exécution et vous mourez un peu à l'intérieur.

Jordan Grey
la source
1
Je pense que rcurry et ncurry sur les fonctions avec des arguments> 2 démontrent que ce n'est vraiment qu'une application partielle et non currying
jk.
@jk En fait, il est démontrable sur des fonctions avec des arguments == 2, comme je le note à la fin. :)
Jordan Grey
3
@matcauthon À strictement parler, le "but" du currying est de transformer une fonction avec de nombreux arguments en une chaîne de fonctions imbriquée avec un argument chacune. Je pense que ce que vous demandez est une raison pratique que vous voulez voulez utiliser Currying, ce qui est un peu plus difficile à justifier Groovy que par exemple , dans LISP ou Haskell. Le fait est que ce que vous voudrez probablement utiliser la plupart du temps est une application partielle, pas un curry.
Jordan Gray
4
+1 pourand you die a little inside
Thomas Eding
1
Wow, je comprends mieux le curry après avoir lu votre réponse: +1 et merci! Spécifique à la question, j'aime bien ce que vous avez dit, "fusionner le curry avec une application partielle".
GlenPeterson
3

Je pense qu'il est clair que le curry groovy est en fait une application partielle lorsque l'on considère des fonctions avec plus de deux arguments. considérer

f :: (a,b,c) -> d

sa forme curry serait

fcurried :: a -> b -> c -> d

mais le curry de groovy retournera quelque chose d'équivalent à (en supposant appelé avec 1 argument x)

fgroovy :: (b,c) -> d 

qui appellera f avec la valeur d'un fixe à x

c'est-à-dire que si le curry de groovy peut renvoyer des fonctions avec des arguments N-1, les fonctions curry correctement n'ont que 1 argument, donc groovy ne peut pas être curry avec curry

jk.
la source
2

Groovy a emprunté la dénomination de ses méthodes de curry à de nombreux autres langages FP non purs qui utilisent également une dénomination similaire pour une application partielle - peut-être malheureux pour une telle fonctionnalité centrée sur FP. Plusieurs implémentations de curry "réelles" sont proposées pour inclusion dans Groovy. Un bon fil pour commencer à lire à leur sujet est ici:

http://groovy.markmail.org/thread/c4ycxdzm3ack6xxb

La fonctionnalité existante restera sous une certaine forme et la compatibilité ascendante sera prise en considération lors d'un appel sur le nom des nouvelles méthodes, etc. - je ne peux donc pas dire à ce stade quelle sera la dénomination finale des nouvelles / anciennes méthodes. être. Probablement un compromis sur la dénomination mais nous verrons.

Pour la plupart des programmeurs OO, la distinction entre les deux termes (curry et application partielle) est sans doute largement académique; cependant, une fois que vous y êtes habitué (et que celui qui maintiendra votre code est formé pour lire ce style de codage), alors la programmation de style sans point ou tacite (qui prend en charge le "vrai" currying) permet à certains types d'algorithmes d'être exprimés de manière plus compacte. et dans certains cas plus élégamment. Il y a évidemment une certaine «beauté réside dans les yeux du spectateur», mais avoir la capacité de prendre en charge les deux styles est conforme à la nature de Groovy (OO / FP, statique / dynamique, classes / scripts, etc.).

Paul King
la source
1

Compte tenu de cette définition trouvée chez IBM:

Le terme curry est emprunté à Haskell Curry, le mathématicien qui a développé le concept de fonctions partielles. Le curry se réfère à la prise de plusieurs arguments dans une fonction qui prend de nombreux arguments, résultant en une nouvelle fonction qui prend les arguments restants et renvoie un résultat.

halverest votre nouvelle fonction (ou fermeture) (au curry), qui ne prend désormais qu'un seul paramètre. L'appel halver(10)entraînerait 5.

Pour cela, il transforme une fonction avec n arguments en une fonction avec n-1 arguments. La même chose est dite par votre exemple haskell ce que fait le curry.

matcauthon
la source
4
La définition d'IBM est incorrecte. Ce qu'ils définissent comme currying est en fait une application de fonction partielle, qui lie (corrige) les arguments d'une fonction pour créer une fonction avec une plus petite arité. Currying transforme une fonction qui prend plusieurs arguments en une chaîne de fonctions qui prennent chacune un argument.
Jordan Gray
1
Dans la définition de wikipedia, c'est la même chose que chez IBM: en mathématiques et en informatique, le curry est la technique de transformation d'une fonction qui prend plusieurs arguments (ou un n-tuple d'arguments) de telle manière qu'elle peut être appelée chaîne de fonctions chacune avec un seul argument (application partielle). Groovy transforme une fonction (avec deux arguments) avec la rcurryfonction (qui prend un argument) en fonction (avec maintenant un seul argument). J'ai enchaîné la fonction curry avec un argument à ma fonction de base pour obtenir ma fonction résultante.
matcauthon
3
Non, la définition de Wikipédia est différente - l'application partielle est lorsque vous appelez la fonction curry - pas lorsque vous la définissez, ce que fait groovy
jk.
6
@jk est correct. Lisez à nouveau l'explication de Wikipedia et vous verrez que ce qui est renvoyé est une chaîne de fonctions avec un argument chacune, pas une fonction avec des n - 1arguments. Voir l'exemple à la fin de ma réponse; voir également plus loin dans l'article pour en savoir plus sur la distinction qui est faite. en.wikipedia.org/wiki/…
Jordan Grey
4
C'est très important, croyez-moi. Encore une fois, le code à la fin de ma réponse montre comment une implémentation correcte fonctionnerait; cela ne prendrait aucun argument, pour une chose. L'implémentation actuelle devrait vraiment être nommée par exemple partial.
Jordan Gray