Quelle est la particularité du curry ou de l'application partielle?

9

J'ai lu des articles sur la programmation fonctionnelle tous les jours et j'ai essayé d'appliquer autant que possible certaines pratiques. Mais je ne comprends pas ce qui est unique au curry ou en application partielle.

Prenez ce code Groovy comme exemple:

def mul = { a, b -> a * b }
def tripler1 = mul.curry(3)
def tripler2 = { mul(3, it) }

Je ne comprends pas quelle est la différence entre tripler1et tripler2. Ne sont-ils pas tous les deux les mêmes? Le 'currying' est supporté dans des langages fonctionnels purs ou partiels comme Groovy, Scala, Haskell etc. Mais je peux faire la même chose (curry gauche, curry droit, curry n ou application partielle) en créant simplement un autre nommé ou anonyme fonction ou fermeture qui transmettra les paramètres à la fonction d'origine (comme tripler2) dans la plupart des langues (même C.)

Est-ce que j'ai râté quelque chose? Il y a des endroits où je peux utiliser le curry et l'application partielle dans mon application Grails mais j'hésite à le faire parce que je me demande "En quoi est-ce différent?"

Veuillez m'éclairer.

EDIT: Êtes-vous en train de dire que l'application / le curry partiel est tout simplement plus efficace que de créer / appeler une autre fonction qui transmet les paramètres par défaut à la fonction d'origine?

Vigneshwaran
la source
1
Quelqu'un peut-il créer les balises "curry" ou "currying"?
Vigneshwaran
Comment curry en C?
Giorgio
c'est probablement plus sur les programmeurs d'
jk.
1
@Vigneshwaran: AFAIK vous n'avez pas besoin de créer une autre fonction dans un langage supportant le curry. Par exemple, dans Haskell f x y = x + ysignifie que fc'est une fonction qui prend un paramètre int. Le résultat de f x( fappliqué à x) est une fonction qui prend un paramètre int. Le résultat f x y(ou (f x) y, c'est- f xà- dire appliqué à y) est une expression qui ne prend aucun paramètre d'entrée et est évaluée en réduisant x + y.
Giorgio
1
Vous pouvez réaliser les mêmes choses, mais la quantité d'effort que vous passez avec C est beaucoup plus douloureuse et pas aussi efficace que dans un langage comme haskell où c'est le comportement par défaut
Daniel Gratzer

Réponses:

8

Le curry consiste à transformer / représenter une fonction qui prend n entrées en n fonctions qui prennent chacune 1 entrée. Une application partielle consiste à corriger certaines des entrées d'une fonction.

La motivation pour une application partielle est principalement qu'elle facilite l'écriture de bibliothèques de fonctions d'ordre supérieur. Par exemple, les algorithmes en C ++ STL prennent tous en grande partie des prédicats ou des fonctions unaires, bind1st permet à l'utilisateur de la bibliothèque de connecter des fonctions non unaires avec une valeur liée. L'écrivain de bibliothèque n'a donc pas besoin de fournir des fonctions surchargées pour tous les algorithmes qui prennent des fonctions unaires pour fournir des versions binaires

Le curry lui-même est utile car il vous donne une application partielle partout où vous le voulez gratuitement, c'est-à-dire que vous n'avez plus besoin d'une fonction comme bind1stappliquer partiellement.

jk.
la source
est curryingquelque chose de spécifique au groovy ou applicable à toutes les langues?
amphibient
@foampile c'est quelque chose qui est applicable dans toutes les langues, mais ironiquement le curry groovy ne le fait pas vraiment programmers.stackexchange.com/questions/152868/…
jk.
@jk. Êtes-vous en train de dire que le curry / application partielle est plus efficace que de créer et d'appeler une autre fonction?
Vigneshwaran
2
@Vigneshwaran - ce n'est pas nécessairement plus performant, mais c'est certainement plus efficace en termes de temps du programmeur. Notez également que le curry est pris en charge par de nombreux langages fonctionnels, mais n'est généralement pas pris en charge dans les langages OO ou procéduraux. (Ou du moins, pas par la langue elle-même.)
Stephen C
6

Mais je peux faire la même chose (curry gauche, curry droit, curry n ou application partielle) en créant simplement une autre fonction ou fermeture nommée ou anonyme qui transmettra les paramètres à la fonction d'origine (comme tripler2) dans la plupart des langues ( même C.)

Et l'optimiseur examinera cela et passera rapidement à quelque chose qu'il pourra comprendre. Le curry est une petite astuce intéressante pour l'utilisateur final, mais présente de bien meilleurs avantages du point de vue de la conception du langage. C'est vraiment bien de gérer toutes les méthodes comme unaires, A -> Boù il Bpeut y avoir une autre méthode.

Il simplifie les méthodes à écrire pour gérer les fonctions d'ordre supérieur. Votre analyse statique et votre optimisation dans le langage n'ont qu'un seul chemin avec lequel se comporter de manière connue. La liaison de paramètres tombe simplement hors de la conception plutôt que d'exiger des cercles pour effectuer ce comportement commun.

Telastyn
la source
6

Comme @jk. mentionné, le curry peut aider à rendre le code plus général.

Par exemple, supposons que vous disposiez de ces trois fonctions (dans Haskell):

> let q a b = (2 + a) * b

> let r g = g 3

> let f a b = b (a 1)

La fonction fprend ici deux fonctions comme arguments, passe 1à la première fonction et transmet le résultat du premier appel à la deuxième fonction.

Si nous devions appeler fusing qet rcomme arguments, ce serait effectivement:

> r (q 1)

qserait appliqué à 1et retourner une autre fonction (comme qc'est curry); cette fonction retournée serait alors passée à rson argument pour recevoir un argument de 3. Le résultat serait une valeur de 9.

Maintenant, disons que nous avions deux autres fonctions:

> let s a = 3 * a

> let t a = 4 + a

nous pourrions également les passer à fet obtenir une valeur de 7ou 15, selon que nos arguments étaient s tou t s. Étant donné que ces fonctions renvoient toutes les deux une valeur plutôt qu'une fonction, aucune application partielle n'aurait lieu dans f s tou f t s.

Si nous avions écrit favec qet ren tête, nous aurions pu utiliser une lambda (fonction anonyme) au lieu d'une application partielle, par exemple:

> let f' a b = b (\x -> a 1 x)

mais cela aurait restreint la généralité de f'. fpeut être appelé avec des arguments qet rou set t, mais f'ne peut être appelé qu'avec qet r- f' s tet les f' t sdeux entraînent une erreur.

PLUS

Si f'étaient appelés avec une paire q'/ r'où le a q'pris plus de deux arguments, le q'serait finalement appliqué partiellement f'.

Alternativement, vous pouvez envelopper l' qextérieur fplutôt que l'intérieur, mais cela vous laisserait avec un lambda imbriqué méchant:

f (\x -> (\y -> q x y)) r

qui est essentiellement ce que le curry qétait en premier lieu!

Paul
la source
Tu m'as ouvert les yeux. Votre réponse m'a fait réaliser à quel point les fonctions au curry / partiellement appliquées sont différentes de la création d'une nouvelle fonction qui transmet des arguments à la fonction d'origine. 1. Passer autour des fonctions curryied / paed (comme f (q.curry (2)) est plus net que de créer des fonctions séparées inutilement pour une utilisation temporaire uniquement (dans des langages fonctionnels comme groovy)
Vigneshwaran
2. Dans ma question, j'ai dit: "Je peux faire la même chose en C." Oui, mais dans les langages non fonctionnels, où vous ne pouvez pas faire circuler les fonctions en tant que données, créer une fonction distincte, qui transmet les paramètres à l'original, n'a pas tous les avantages du curry / pa
Vigneshwaran
J'ai remarqué que Groovy ne prend pas en charge le type de généralisation pris en charge par Haskell. Je devais écrire def f = { a, b -> b a.curry(1) }pour faire f q, rtravailler def f = { a, b -> b a(1) }ou def f = { a, b -> b a.curry(1)() }pour f s, ttravailler. Vous devez passer tous les paramètres ou dire explicitement que vous êtes au curry. :(
Vigneshwaran
2
@Vigneshwaran: Oui, il est sûr de dire que Haskell et le curry vont très bien ensemble . ;] Notez que dans Haskell, les fonctions sont curry (dans la définition correcte) par défaut et un espace indique l'application de la fonction, donc f x ysignifie ce que de nombreuses langues écriraient f(x)(y), non f(x, y). Peut-être que votre code fonctionnerait dans Groovy si vous écrivez qpour qu'il s'attende à être appelé comme q(1)(2)?
CA McCann
1
@Vigneshwaran Heureux d'avoir pu aider! Je ressens votre peine d'avoir à dire explicitement que vous faites une demande partielle. Dans Clojure, je dois le faire (partial f a b ...)- étant habitué à Haskell, je manque beaucoup de curry approprié lors de la programmation dans d'autres langages (bien que je travaille récemment en F # qui, heureusement, le supporte).
Paul
3

Il y a deux points clés concernant une application partielle. Le premier est syntaxique / pratique - certaines définitions deviennent plus faciles et plus courtes à lire et à écrire, comme l'a mentionné @jk. (Consultez la programmation Pointfree pour en savoir plus sur la façon dont c'est génial!)

Le deuxième, comme l'a mentionné @telastyn, concerne un modèle de fonctions et n'est pas simplement pratique. Dans la version Haskell, dont je vais récupérer mes exemples car je ne connais pas les autres langages à application partielle, toutes les fonctions prennent un seul argument. Oui, même des fonctions comme:

(:) :: a -> [a] -> [a]

prendre un seul argument; en raison de l'associativité du constructeur de type de fonction ->, ce qui précède équivaut à:

(:) :: a -> ([a] -> [a])

qui est une fonction qui prend un aet retourne une fonction [a] -> [a].

Cela nous permet d'écrire des fonctions comme:

($) :: (a -> b) -> a -> b

qui peut appliquer n'importe quelle fonction à un argument du type approprié. Même les plus fous comme:

f :: (t, t1) -> t -> t1 -> (t2 -> t3 -> (t, t1)) -> t2 -> t3 -> [(t, t1)]
f q r s t u v = q : (r, s) : [t u v]

f' :: () -> Char -> (t2 -> t3 -> ((), Char)) -> t2 -> t3 -> [((), Char)]
f' = f $ ((), 'a')  -- <== works fine

D'accord, c'était donc un exemple artificiel. Mais une plus utile implique la classe de type Applicative , qui inclut cette méthode:

(<*>) :: Applicative f => f (a -> b) -> f a -> f b

Comme vous pouvez le voir, le type est identique, comme $si vous enlevez le Applicative fbit, et en fait, cette classe décrit l'application de fonction dans un contexte. Donc, au lieu d'une application de fonction normale:

ghci> map (+3) [1..5]  
[4,5,6,7,8]

Nous pouvons appliquer des fonctions dans un contexte applicatif; par exemple, dans le contexte Peut-être dans lequel quelque chose peut être présent ou manquant:

ghci> Just map <*> Just (+3) <*> Just [1..5]
Just [4,5,6,7,8]

ghci> Just map <*> Nothing <*> Just [1..5]
Nothing

Maintenant, la partie vraiment cool est que la classe de type Applicative ne mentionne rien sur les fonctions de plus d'un argument - néanmoins, elle peut les traiter, même les fonctions de 6 arguments comme f:

fA' :: Maybe (() -> Char -> (t2 -> t3 -> ((), Char)) -> t2 -> t3 -> [((), Char)])
fA' = Just f <*> Just ((), 'a')

Pour autant que je sache, la classe de type Applicative dans sa forme générale ne serait pas possible sans une conception de l'application partielle. (À tous les experts en programmation - corrigez-moi si je me trompe!) Bien sûr, si votre langue manque d'application partielle, vous pouvez la créer sous une forme ou une autre, mais ... ce n'est tout simplement pas la même chose, n'est-ce pas? ? :)


la source
1
Applicativesans curry ou application partielle utiliserait fzip :: (f a, f b) -> f (a, b). Dans un langage avec des fonctions d'ordre supérieur, cela vous permet de déplacer le curry et l'application partielle dans le contexte du foncteur et est équivalent à (<*>). Sans fonctions d'ordre supérieur, vous n'auriez pas, fmapdonc le tout serait inutile.
CA McCann
@CAMcCann merci pour la rétroaction! Je savais que j'étais au-dessus de ma tête avec cette réponse. Alors, ce que j'ai dit est faux?
1
C'est correct dans l'esprit, certainement. Diviser les cheveux sur les définitions de "forme générale", "possible" et avoir une "conception d'application partielle" ne changera pas le simple fait que le charmant f <$> x <*> ystyle idiomatique fonctionne facilement parce que le curry et l'application partielle fonctionnent facilement. En d'autres termes, ce qui est agréable est plus important que ce qui est possible ici.
CA McCann
Chaque fois que je vois des exemples de code de programmation fonctionnelle, je suis plus convaincu que c'est une blague élaborée et qu'elle n'existe pas.
Kieveli
1
@Kieveli, c'est dommage que vous vous sentiez de cette façon. Il existe de nombreux bons didacticiels qui vous permettront de vous familiariser avec les bases.