Je viens d’apprendre le curry et, même si je pense comprendre le concept, je ne vois aucun avantage à l’utiliser.
Comme exemple trivial, j’utilise une fonction qui ajoute deux valeurs (écrites en ML). La version sans currying serait
fun add(x, y) = x + y
et serait appelé comme
add(3, 5)
tandis que la version au curry est
fun add x y = x + y
(* short for val add = fn x => fn y=> x + y *)
et serait appelé comme
add 3 5
Il me semble que ce n'est qu'un sucre syntaxique qui supprime un ensemble de parenthèses de la définition et de l'appel de la fonction. J'ai vu le curry être répertorié comme l'une des caractéristiques importantes d'un langage fonctionnel, et je suis un peu décontenancé par cela pour le moment. Le concept de création d'une chaîne de fonctions consommant chacune un paramètre unique, au lieu d'une fonction prenant un tuple, semble plutôt compliqué à utiliser pour un simple changement de syntaxe.
La syntaxe légèrement plus simple est-elle la seule motivation du currying ou manque-t-il d'autres avantages qui ne sont pas évidents dans mon exemple très simple? Le curry est-il juste du sucre syntaxique?
la source
Réponses:
Avec les fonctions au curry, vous obtenez une réutilisation plus facile de fonctions plus abstraites, puisque vous devez vous spécialiser. Disons que vous avez une fonction d'ajout
et que vous voulez ajouter 2 à chaque membre d'une liste. En Haskell, vous feriez ceci:
Ici la syntaxe est plus claire que si vous deviez créer une fonction
add2
ou si vous deviez créer une fonction lambda anonyme:
Il vous permet également d’abstraire de différentes implémentations. Disons que vous avez deux fonctions de recherche. Une à partir d'une liste de paires clé / valeur et une clé à une valeur et une autre à une carte de clés à valeurs et une clé à une valeur, comme ceci:
Ensuite, vous pouvez créer une fonction qui accepte une fonction de recherche de clé en valeur. Vous pouvez lui transmettre n'importe laquelle des fonctions de recherche ci-dessus, partiellement appliquées avec une liste ou une carte, respectivement:
En conclusion: le curry est bon, car il vous permet de spécialiser / d'appliquer partiellement des fonctions en utilisant une syntaxe légère, puis de transférer ces fonctions partiellement appliquées à des fonctions d'ordre supérieur telles que
map
oufilter
. Les fonctions d'ordre supérieur (qui prennent des fonctions en tant que paramètres ou les produisent en tant que résultats) constituent la base de la programmation fonctionnelle. De plus, les fonctions currying et partiellement appliquées permettent une utilisation beaucoup plus efficace et concise des fonctions d'ordre supérieur.la source
La réponse pratique est que le curry facilite la création de fonctions anonymes. Même avec une syntaxe lambda minimale, c'est une victoire; comparer:
Si vous avez une vilaine syntaxe lambda, c'est encore pire. (Je vous regarde, JavaScript, Scheme et Python.)
Cela devient de plus en plus utile à mesure que vous utilisez de plus en plus de fonctions d'ordre supérieur. Bien que j'utilise plus de fonctions d'ordre supérieur dans Haskell que dans d'autres langages, j'ai constaté que j'utilisais moins la syntaxe lambda, car, dans les deux tiers des cas, le lambda serait simplement une fonction partiellement appliquée. (Et une grande partie de l'autre fois, je l'extrais dans une fonction nommée.)
Plus fondamentalement, il n'est pas toujours évident de savoir quelle version d'une fonction est "canonique". Par exemple, prenez
map
. Le type demap
peut être écrit de deux manières:Lequel est le "correct"? C'est difficile à dire. En pratique, la plupart des langues utilisent la première - map prend une fonction et une liste et retourne une liste. Cependant, fondamentalement, ce que fait réellement la carte, c’est associer les fonctions normales à la liste des fonctions - elle prend une fonction et retourne une fonction. Si la carte est au curry, vous n'avez pas à répondre à cette question: les deux , d'une manière très élégante.
Cela devient particulièrement important une fois que vous généralisez
map
à des types autres que list.En outre, currying n'est vraiment pas très compliqué. C'est en fait un peu une simplification par rapport au modèle utilisé par la plupart des langages: vous n'avez besoin d'aucune notion de fonctions à arguments multiples intégrés dans votre langage. Cela reflète également de plus près le calcul lambda sous-jacent.
Bien entendu, les langages de type ML ne disposent pas de la notion d'arguments multiples sous forme curry ou non. La
f(a, b, c)
syntaxe correspond en fait à la transmission du tuple(a, b, c)
enf
, donc n'acceptef
toujours que des arguments. C’est en fait une distinction très utile que je souhaiterais que d’autres langues aient parce qu’il est très naturel d’écrire quelque chose comme:Vous ne pourriez pas facilement faire cela avec des langues qui ont l’idée de multiples arguments tout de suite!
la source
Le currying peut être utile si vous transmettez une fonction en tant qu'objet de première classe et que vous ne recevez pas tous les paramètres nécessaires pour l'évaluer à un endroit précis du code. Vous pouvez simplement appliquer un ou plusieurs paramètres lorsque vous les obtenez et transmettre le résultat à un autre élément de code comportant davantage de paramètres, puis terminez son évaluation.
Le code pour accomplir ceci sera plus simple que si vous devez d'abord rassembler tous les paramètres.
En outre, il est possible que le code soit réutilisé davantage, car les fonctions prenant un seul paramètre (une autre fonction curry) ne doivent pas nécessairement correspondre de manière spécifique à tous les paramètres.
la source
La motivation principale (au moins au début) pour le currying n’était pas pratique mais théorique. En particulier, le currying vous permet d’obtenir efficacement des fonctions multi-arguments sans définir réellement la sémantique ni pour les sémantiques des produits. Cela conduit à un langage plus simple avec autant d’expressivité qu’un autre langage plus compliqué, ce qui est souhaitable.
la source
(Je vais donner des exemples en Haskell.)
Lorsque vous utilisez des langages fonctionnels, il est très pratique d'appliquer partiellement une fonction. Comme dans Haskell, il
(== x)
y a une fonction qui retourneTrue
si son argument est égal à un terme donnéx
:sans currying, nous aurions un code un peu moins lisible:
Ceci est lié à la programmation tacite (voir aussi le style Pointfree sur le wiki Haskell). Ce style ne se concentre pas sur les valeurs représentées par des variables, mais sur la composition de fonctions et la manière dont l’information circule dans une chaîne de fonctions. Nous pouvons convertir notre exemple en un formulaire qui n'utilise pas de variables du tout:
Nous considérons ici
==
en fonction dea
laa -> Bool
etany
en fonction dea -> Bool
la[a] -> Bool
. En les composant simplement, nous obtenons le résultat. Tout cela grâce au currying.L'inverse, sans currying, est également utile dans certaines situations. Par exemple, supposons que nous voulions diviser une liste en deux parties - des éléments inférieurs à 10 et le reste, puis concaténer ces deux listes. La scission de la liste se fait par (ici, nous utilisons aussi le curry ). Le résultat est de type . Au lieu d'extraire le résultat dans sa première et la deuxième partie et en les combinant à l' aide , nous pouvons le faire directement par uncurrying comme
partition
(< 10)
<
([Int],[Int])
++
++
En effet,
(uncurry (++) . partition (< 10)) [4,12,11,1]
évalue à[4,1,12,11]
.Il existe également d’importants avantages théoriques:
(a, b) -> c
ena -> (b -> c)
signifie que le résultat de cette dernière fonction est de typeb -> c
. En d'autres termes, le résultat est une fonction.la source
mem x lst = any (\y -> y == x) lst
? (Avec une barre oblique inverse).Le curry n'est pas qu'un sucre syntaxique!
Considérez les signatures de type
add1
(non pressé) etadd2
(au curry):(Dans les deux cas, les parenthèses dans la signature de type sont facultatives, mais je les ai incluses par souci de clarté.)
add1
est une fonction qui prend un 2-tuple deint
etint
et renvoie unint
.add2
est une fonction qui prend unint
et retourne une autre fonction qui à son tour prend unint
et retourne unint
.La différence essentielle entre les deux devient plus visible lorsque nous spécifions explicitement l'application de la fonction. Définissons une fonction (non curried) qui applique son premier argument à son second argument:
Maintenant, nous pouvons voir la différence entre
add1
etadd2
plus clairement.add1
se fait appeler avec un 2 tuple:mais
add2
est appelé avec unint
puis sa valeur de retour est appelée avec un autreint
:EDIT: L’avantage essentiel du curry est que vous bénéficiez d’une application partielle gratuite. Disons que vous vouliez une fonction de type
int -> int
(disons,map
sur une liste) qui ajoute 5 à son paramètre. Vous pouvez écrireaddFiveToParam x = x+5
ou faire l'équivalent avec un lambda en ligne, mais vous pouvez aussi beaucoup plus facilement écrire (surtout dans les cas moins triviaux que celui-ci)add2 5
!la source
Le curry n'est que du sucre syntaxique, mais je pense que vous comprenez mal ce que le sucre fait. En prenant votre exemple,
est en fait le sucre syntaxique pour
C'est-à-dire que (add x) renvoie une fonction qui prend un argument y et ajoute x à y.
C'est une fonction qui prend un tuple et ajoute ses éléments. Ces deux fonctions sont en réalité assez différentes; ils prennent des arguments différents.
Si vous vouliez ajouter 2 à tous les numéros d'une liste:
Le résultat serait
[3,4,5]
.Par contre, si vous voulez additionner chaque tuple dans une liste, la fonction addTuple convient parfaitement.
Le résultat serait
[12,13,14]
.Les fonctions de curry sont excellentes lorsqu'une application partielle est utile - par exemple map, fold, app, filter. Considérons cette fonction, qui renvoie le plus grand nombre positif de la liste fournie ou 0 s'il n'y a pas de nombres positifs:
la source
Une autre chose que je n'ai pas encore vue mentionnée est que le curry permet une abstraction (limitée) de l'arité.
Considérez ces fonctions qui font partie de la bibliothèque de Haskell
Dans chaque cas, la variable type
c
peut être un type de fonction afin que ces fonctions fonctionnent sur un préfixe de la liste de paramètres de leur argument. Sans vous lancer dans le curry, vous avez besoin d'une fonctionnalité de langage spéciale pour résumer l'arité des fonctions ou vous avez différentes versions de ces fonctions spécialisées pour différentes arités.la source
Ma compréhension limitée est telle:
1) Application de fonction partielle
Application de fonction partielle est le processus de retour d'une fonction qui prend un nombre d'arguments inférieur. Si vous fournissez 2 arguments sur 3, une fonction prenant 3-2 = 1 argument sera renvoyée. Si vous fournissez 1 argument sur 3, il retournera une fonction prenant 3-1 = 2 arguments. Si vous le souhaitez, vous pouvez même appliquer partiellement 3 arguments sur 3 et renvoyer une fonction qui ne prend aucun argument.
Donc, étant donné la fonction suivante:
En liant 1 à x et en appliquant partiellement cela à la fonction ci-dessus,
f(x,y,z)
vous obtenez:Où:
f'(y,z) = 1 + y + z;
Maintenant, si vous liez y à 2 et z à 3 et appliquiez partiellement,
f'(y,z)
vous obtiendriez:Où:
f''() = 1 + 2 + 3
;Maintenant, à tout moment, vous pouvez choisir d’évaluer
f
,f'
ouf''
. Donc je peux faire:ou
2) curry
Le curry , d’autre part, est le processus consistant à diviser une fonction en une chaîne imbriquée de fonctions à un argument. Vous ne pouvez jamais fournir plus d'un argument, c'est un ou zéro.
Donc, étant donné la même fonction:
Si vous le faisiez curry, vous auriez une chaîne de 3 fonctions:
Où:
Maintenant, si vous appelez
f'(x)
avecx = 1
:Vous êtes renvoyé une nouvelle fonction:
Si vous appelez
g(y)
avecy = 2
:Vous êtes renvoyé une nouvelle fonction:
Enfin si vous appelez
h(z)
avecz = 3
:Vous revenez
6
.3) fermeture
Enfin, la fermeture est le processus de capture d’une fonction et de données en une seule unité. Une fermeture de fonction peut prendre 0 à un nombre infini d'arguments, mais elle est également consciente des données qui ne lui sont pas transmises.
Encore une fois, étant donné la même fonction:
Vous pouvez plutôt écrire une fermeture:
Où:
f'
est fermé surx
. Signification quif'
peut lire la valeur de x qui se trouve à l'intérieurf
.Donc, si vous appelez
f
avecx = 1
:Vous obtiendrez une fermeture:
Maintenant, si vous avez appelé
closureOfF
avecy = 2
etz = 3
:Qui reviendrait
6
Conclusion
Currying, application partielle et fermetures sont toutes assez similaires en ce sens qu'elles décomposent une fonction en plusieurs parties.
Currying décompose une fonction de plusieurs arguments en fonctions imbriquées d’arguments uniques qui renvoient des fonctions d’arguments uniques. Il ne sert à rien de traiter une fonction d'un argument ou moins, car cela n'a aucun sens.
L'application partielle décompose une fonction de plusieurs arguments en une fonction de petits arguments dont les arguments manquants ont été substitués à la valeur fournie.
Closure décompose une fonction en une fonction et un jeu de données dans lequel les variables de la fonction qui n'ont pas été passées peuvent regarder à l'intérieur du jeu de données pour trouver une valeur à lier lorsqu'elles sont invitées à évaluer.
Ce qui est déroutant à propos de tout cela, c'est qu'ils peuvent en quelque sorte être utilisés pour implémenter un sous-ensemble des autres. Donc, essentiellement, ils sont tous un peu un détail de mise en œuvre. Ils offrent tous une valeur similaire en ce sens qu'il n'est pas nécessaire de rassembler toutes les valeurs à l'avance et que vous pouvez réutiliser une partie de la fonction, car vous l'avez décomposée en unités discrètes.
Divulgation
Je ne suis en aucun cas un expert du sujet, je n’ai que récemment commencé à en apprendre davantage sur ce sujet, et j’exprime donc mes connaissances actuelles, mais il pourrait y avoir des erreurs que je vous invite à signaler, et je corrigerai si / si Je découvre tout.
la source
Le currying (application partielle) vous permet de créer une nouvelle fonction à partir d’une fonction existante en fixant certains paramètres. Il s'agit d'un cas particulier de fermeture lexicale où la fonction anonyme est juste un wrapper trivial qui transmet certains arguments capturés à une autre fonction. Nous pouvons également le faire en utilisant la syntaxe générale pour effectuer des fermetures lexicales, mais une application partielle fournit un sucre syntaxique simplifié.
C'est pourquoi les programmeurs Lisp, lorsqu'ils travaillent dans un style fonctionnel, utilisent parfois des bibliothèques pour des applications partielles .
Au lieu de
(lambda (x) (+ 3 x))
, ce qui nous donne une fonction qui ajoute 3 à son argument, vous pouvez écrire quelque chose comme(op + 3)
, et donc ajouter 3 à chaque élément d'une liste serait alors(mapcar (op + 3) some-list)
plutôt que(mapcar (lambda (x) (+ 3 x)) some-list)
. Cetteop
macro fera de vous une fonction qui prend des argumentsx y z ...
et appelle(+ a x y z ...)
.Dans de nombreux langages purement fonctionnels, une application partielle est intégrée à la syntaxe, de sorte qu'il n'y a pas d'
op
opérateur. Pour déclencher une application partielle, vous appelez simplement une fonction avec moins d'arguments que nécessaire. Au lieu de produire une"insufficient number of arguments"
erreur, le résultat est une fonction des arguments restants.la source
a -> b -> c
n'a pas le paramètre s ( au pluriel), il n'a qu'un seul paramètre,c
. Lorsqu'il est appelé, il retourne une fonction de typea -> b
.Pour la fonction
Il est de la forme
f': 'a * 'b -> 'c
Pour évaluer on va faire
Pour la fonction au curry
Pour évaluer on va faire
S’il s’agit d’un calcul partiel, en particulier (3 + y), auquel on peut alors compléter le calcul avec
ajouter dans le second cas est de la forme
f: 'a -> 'b -> 'c
Ce que le currying fait ici est de transformer une fonction qui prend deux accords en un seul accord qui ne donne qu'un résultat. Évaluation partielle
Say
x
on the RHS n'est pas juste un int régulier, mais un calcul complexe qui prend un certain temps à compléter, pour augmenter, deux secondes.Donc, la fonction ressemble maintenant à
De type
add : int * int -> int
Maintenant, nous voulons calculer cette fonction pour une plage de nombres, mappons-la
Pour ce qui précède, le résultat de
twoSecondsComputation
est évalué à chaque fois. Cela signifie que cela prend 6 secondes pour ce calcul.En combinant la mise en scène et le curry, on peut éviter cela.
De la forme au curry
add : int -> int -> int
Maintenant on peut faire,
Le
twoSecondsComputation
seul doit être évalué une fois. Pour augmenter l'échelle, remplacez deux secondes par 15 minutes ou une heure, puis créez une carte avec 100 chiffres.Résumé : Le curry est excellent lorsque vous utilisez d’autres méthodes pour les fonctions de niveau supérieur en tant qu’outil d’évaluation partielle. Son but ne peut pas vraiment être démontré par lui-même.
la source
Le curry permet une composition fonctionnelle flexible.
J'ai composé une fonction "curry". Dans ce contexte, je me fiche de quel type d’enregistreur je reçois ou d’où il provient. Je me fiche de l'action ou de sa provenance. Tout ce qui m'importe, c'est de traiter mes entrées.
La variable de générateur est une fonction qui retourne une fonction qui retourne une fonction prenant mon entrée qui fait mon travail. Ceci est un exemple simple et utile et non un objet en vue.
la source
Le curry est un avantage lorsque vous n'avez pas tous les arguments pour une fonction. Si vous évaluez pleinement la fonction, il n'y a pas de différence significative.
Le currying vous permet d'éviter de mentionner des paramètres qui ne sont pas encore nécessaires. Il est plus concis et n'exige pas de trouver un nom de paramètre qui n'entre pas en conflit avec une autre variable dans la portée (ce qui est mon avantage préféré).
Par exemple, lorsque vous utilisez des fonctions qui prennent des fonctions en arguments, vous vous retrouvez souvent dans des situations où vous avez besoin de fonctions telles que "ajouter 3 à l'entrée" ou "comparer l'entrée à la variable v". Avec currying, ces fonctions s’écrit facilement:
add 3
et(== v)
. Sans currying, vous devez utiliser des expressions lambda:x => add 3 x
etx => x == v
. Les expressions lambda sont deux fois plus longues et nécessitent un peu de travail lié à la sélection d'un nom,x
si ce n'est déjà le casx
.Un avantage supplémentaire des langages basés sur le currying est que, lors de l'écriture de code générique pour des fonctions, vous ne vous retrouvez pas avec des centaines de variantes basées sur le nombre de paramètres. Par exemple, en C #, une méthode "curry" aurait besoin de variantes pour Func <R>, Func <A, R>, Func <A1, A2, R>, Func <A1, A2, A3, R>, etc. pour toujours. En Haskell, l’équivalent d’un Func <A1, A2, R> ressemble davantage à un Func <Tuple <A1, A2>, R> ou à un Func <A1, Func <A2, R >> (et à un Func <R> ressemble plus à un Func <Unité, R>), de sorte que toutes les variantes correspondent au cas unique Func <A, R>.
la source
Le raisonnement principal auquel je peux penser (et je ne suis en aucun cas un expert sur ce sujet) commence à montrer ses avantages lorsque les fonctions passent de trivial à non trivial. Dans tous les cas triviaux contenant la plupart des concepts de cette nature, vous ne trouverez aucun avantage réel. Cependant, la plupart des langages fonctionnels utilisent beaucoup la pile dans les opérations de traitement. Considérez PostScript ou Lisp comme exemples. En utilisant le curry, les fonctions peuvent être empilées plus efficacement et cet avantage devient évident à mesure que les opérations deviennent de moins en moins triviales. De manière curry, la commande et les arguments peuvent être lancés sur la pile dans l'ordre et supprimés au besoin afin qu'ils soient exécutés dans le bon ordre.
la source
Le curry dépend de manière décisive (définitivement même) de la capacité de retourner une fonction.
Considérez ce pseudo-code (artificiel).
var f = (m, x, b) => ... renvoie quelque chose ...
Disons que l'appel de f avec moins de trois arguments renvoie une fonction.
var g = f (0, 1); // this renvoie une fonction liée à 0 et 1 (m et x) qui accepte un argument supplémentaire (b).
var y = g (42); // invoque g avec le troisième argument manquant, en utilisant 0 et 1 pour m et x
Le fait que vous puissiez partiellement appliquer des arguments et récupérer une fonction réutilisable (liée aux arguments que vous avez fournis) est très utile (et DRY).
la source