Éclaircissement sur les Applicatives

Bonjour, Cela fait maintenant quelques mois que je côtoie Haskell, j'arrive presque toujours à mes fins, mais cela ne me convient pas, je tente donc de prendre le problème à sa racine : la compréhension, ou plutôt l'assimilation des concepts. Je pense que j'ai un soucis à ce niveau car je pense plutôt bien voir comment tout s'emboîte, mais face au code, je suis incapable de l'appliquer ! Je vous laisse juger par vous-même. Je vais tenter d'expliquer ma manière de voir les choses de manière aussi détaillée que possible via un ensemble d'assertions (notées AN où N est le numéro de l'assertion). Le typeclass Applicative à la tête suivante : class Functor f => Applicative f where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b (*>) :: f a -> f b -> f b (<*) :: f a -> f b -> f a A1 : Ce qui signifie que tout type implémentant ces fonctions doivent également implémenter les fonctions de Functor. Q1 : quelle est l'intérêt ? Nous avons pure :: Applicative f => a -> f a A2 : 'f a' signifie de type a implémentant le typeclass f (Applicative dans ce cas précis A3 : Il s'agit "simplement" d'un lifting, d'une encapsulation, grosso-modo de donner un paramètre à un constructeur. Ensuite (<*>) :: Applicative f => f (a -> b) -> f a -> f b A4 : prend une fonction et un paramètre dans un "emballage" implémentant Applicative et applique cette valeur à cette fonction en retournant le type dans un autre "emballage". A5 : cette fonction sert dans deux cas : à fournir un moyen de manipuler le contenu de "boîtes", "décurryfier" dans le sens passer des arguments au fur et à mesure à une fonction. Q2 : Y a-t-il d'autres cas d'application. A6 : <* et *> ne servent qu'à appliquer un des arguments. Q3 : quels sont les cas d'applications typiques ? j'ai du mal à voir Ensuite il y a les fonctions de lifting : liftA :: Applicative f => (a -> b) -> f a -> f b A7 : prend une fonction "normale" à un argument et en fait une fonction maniant une structure encapsulée. Q4 : quel est l'intérêt ? liftA (+1) $ Just 1 n'est pas égale à pure (+1) <*> Just 1 ? Q5 : ou à la version Functor : (+1) <$> Just 1 ? Q6 : dans le cas de version Functor, quel est l'intérêt de la passer Applicative ? liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c A8 : idem mais avec deux arguments A9 : liftA2 (+) (Just 2) (Just 1) est équivalent à liftA (+) (Just 2) <*> Just 1 Q7 : Quel est l'intérêt du coup ? liftA3 :: Applicative f => (a -> b -> c -> d) -> f a -> f b -> f c -> f d A10 : idem mais avec trois arguments Q8 : Quel est l'intérêt de Control.Applicative.Lift ? Il est écrit : "Adding a new kind of pure computation to an applicative functor" Q9 : J'ai du mal à saisir cette notion de calcul, est-ce qu'il est possible de me l'expliquer en deux mots ? Je me rend compte que mon mail est très long, ça fait plus de deux mois que je me casse les dents dessus, j'ai écumé plusieurs articles/livres, pas moyen de me faire une idée claire de tout ça, ou de voir les applications pratiques. Je vous serait très reconnaissant de prendre le temps de me répondre et/ou de valider/invalider mes assertions, Merci par avance. PS : Par la suite j'aurais des question sur Alternative et les Monades PPS : la longueur de la rédaction m'a donné une idée pour Q7 : A11 : liftA2 et liftA3 servent à obtenir des fonctions "passables" à des fonctions qui le demande, ex : myF :: (Maybe a -> Maybe b -> Maybe c) -> a -> b -> Maybe c myF f a b = f (Just a) (Just b) la fonction serait appelable via myF (liftA2 (+)) 1 2

Réponse dans le texte :
2013/12/20 Gautier DI FOLCO
Bonjour,
Cela fait maintenant quelques mois que je côtoie Haskell, j'arrive presque toujours à mes fins, mais cela ne me convient pas, je tente donc de prendre le problème à sa racine : la compréhension, ou plutôt l'assimilation des concepts. Je pense que j'ai un soucis à ce niveau car je pense plutôt bien voir comment tout s'emboîte, mais face au code, je suis incapable de l'appliquer ! Je vous laisse juger par vous-même. Je vais tenter d'expliquer ma manière de voir les choses de manière aussi détaillée que possible via un ensemble d'assertions (notées AN où N est le numéro de l'assertion). Le typeclass Applicative à la tête suivante : class Functor f => Applicative f where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b (*>) :: f a -> f b -> f b (<*) :: f a -> f b -> f a
A1 : Ce qui signifie que tout type implémentant ces fonctions doivent également implémenter les fonctions de Functor. Q1 : quelle est l'intérêt ?
Ce n'est pas vraiment par intérêt plutôt qu'une conséquence du fait qu'un foncteur applicatif a un foncteur sous-jacent. En particulier, le foncteur est utilisé dans l'implémentation par défaut de (*>) et (<*), et une instance correcte doit satisfaire la loi : f <$> x = pure f <*> x On pourrait s'en passer, mais c'est probablement pratique.
Nous avons pure :: Applicative f => a -> f a A2 : 'f a' signifie de type a implémentant le typeclass f (Applicative dans ce cas précis
Hmm pas vraiment. `f a` signifie de type `f a`, où `f` est un foncteur applicatif.
A3 : Il s'agit "simplement" d'un lifting, d'une encapsulation, grosso-modo de donner un paramètre à un constructeur.
Ensuite (<*>) :: Applicative f => f (a -> b) -> f a -> f b A4 : prend une fonction et un paramètre dans un "emballage" implémentant Applicative et applique cette valeur à cette fonction en retournant le type dans un autre "emballage". A5 : cette fonction sert dans deux cas : à fournir un moyen de manipuler le contenu de "boîtes", "décurryfier" dans le sens passer des arguments au fur et à mesure à une fonction. Q2 : Y a-t-il d'autres cas d'application.
Pas vraiment, c'est juste un "lift" de l'application d'une fonction. Donc à utiliser quand tu as une fonction à appliquer... A6 : <* et *> ne servent qu'à appliquer un des arguments.
Q3 : quels sont les cas d'applications typiques ? j'ai du mal à voir
Je ne fais pas assez de Haskell pour savoir où c'est utilisé, mais c'est probablement passé comme argument à une fonction d'ordre supérieur dans des cas spéciaux où on n'a pas trop de choix sur le format des données en entrée et on veut ignorer la moitié... Ensuite il y a les fonctions de lifting :
liftA :: Applicative f => (a -> b) -> f a -> f b A7 : prend une fonction "normale" à un argument et en fait une fonction maniant une structure encapsulée. Q4 : quel est l'intérêt ? liftA (+1) $ Just 1 n'est pas égale à pure (+1) <*> Just 1 ?
Si, mais parfois tu as besoin de `pure` et parfois de `liftA`. Encore une fois, ça dépend de la façon dont c'est utilisé. Évidemment, si tu appliques tout complètement ça semble redondant, mais quand tu appliques partiellement, `liftA (+1)` et `pure (+1)` ont un type différent, tous deux utiles.
Q5 : ou à la version Functor : (+1) <$> Just 1 ? Q6 : dans le cas de version Functor, quel est l'intérêt de la passer Applicative ?
Pas d'intérêt : si tout ce dont tu as besoin c'est `fmap`, utilise `fmap`.
liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c A8 : idem mais avec deux arguments A9 : liftA2 (+) (Just 2) (Just 1) est équivalent à liftA (+) (Just 2) <*> Just 1 Q7 : Quel est l'intérêt du coup ?
Même réponse. Tu sembles chercher à éviter toute redondance. Pourquoi ne pas écrire tout ton code avec juste des lambdas et des applications ? :-)
liftA3 :: Applicative f => (a -> b -> c -> d) -> f a -> f b -> f c -> f d A10 : idem mais avec trois arguments
Q8 : Quel est l'intérêt de Control.Applicative.Lift ? Il est écrit : "Adding a new kind of pure computation to an applicative functor" Q9 : J'ai du mal à saisir cette notion de calcul, est-ce qu'il est possible de me l'expliquer en deux mots ?
Je ne saurai expliquer ça clairement, mais tu peux jeter un coup d'oeil ici : http://hackage.haskell.org/package/transformers-0.3.0.0/docs/src/Control-App... En particulier, l'implémentation de `fmap` et le commentaire du type Errors peuvent te donner une idée de ce qu'il se passe... Je me rend compte que mon mail est très long, ça fait plus de deux mois que
je me casse les dents dessus, j'ai écumé plusieurs articles/livres, pas moyen de me faire une idée claire de tout ça, ou de voir les applications pratiques. Je vous serait très reconnaissant de prendre le temps de me répondre et/ou de valider/invalider mes assertions, Merci par avance.
PS : Par la suite j'aurais des question sur Alternative et les Monades PPS : la longueur de la rédaction m'a donné une idée pour Q7 : A11 : liftA2 et liftA3 servent à obtenir des fonctions "passables" à des fonctions qui le demande, ex : myF :: (Maybe a -> Maybe b -> Maybe c) -> a -> b -> Maybe c myF f a b = f (Just a) (Just b) la fonction serait appelable via myF (liftA2 (+)) 1 2
Exactement ! _______________________________________________
Haskell-fr mailing list Haskell-fr@haskell.org http://www.haskell.org/mailman/listinfo/haskell-fr

Bonsoir,
Merci de la rapidité de ta réponse, la suite dans le texte :
Le 20 décembre 2013 23:16, Valentin Robert
Réponse dans le texte :
2013/12/20 Gautier DI FOLCO
Nous avons pure :: Applicative f => a -> f a A2 : 'f a' signifie de type a implémentant le typeclass f (Applicative dans ce cas précis
Hmm pas vraiment. `f a` signifie de type `f a`, où `f` est un foncteur applicatif.
Donc pour 'f a', 'f' est un type paramétrique qui implémente le typeclass Applicative et qui a un paramètre 'a' ? Ça signifie donc que les types n'ayant pas ou plusieurs paramètre de type ne peuvent pas être utilisé dans cette fonction ? Comme Either ?
liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
A8 : idem mais avec deux arguments A9 : liftA2 (+) (Just 2) (Just 1) est équivalent à liftA (+) (Just 2) <*> Just 1 Q7 : Quel est l'intérêt du coup ?
Même réponse. Tu sembles chercher à éviter toute redondance. Pourquoi ne pas écrire tout ton code avec juste des lambdas et des applications ? :-)
En fait je cherche à savoir pourquoi il y a plusieurs formes, mais visiblement je me pose trop de questions :/
liftA3 :: Applicative f => (a -> b -> c -> d) -> f a -> f b -> f c -> f d A10 : idem mais avec trois arguments
Q8 : Quel est l'intérêt de Control.Applicative.Lift ? Il est écrit : "Adding a new kind of pure computation to an applicative functor" Q9 : J'ai du mal à saisir cette notion de calcul, est-ce qu'il est possible de me l'expliquer en deux mots ?
Je ne saurai expliquer ça clairement, mais tu peux jeter un coup d'oeil ici :
http://hackage.haskell.org/package/transformers-0.3.0.0/docs/src/Control-App...
En particulier, l'implémentation de `fmap` et le commentaire du type Errors peuvent te donner une idée de ce qu'il se passe...
Même une piste m'aiderais :/ J'ai l'impression qu'il s'agit de gérer soit une valeur soit une valeur et une fonction à lui appliquer, mais en quoi la valeur seule serait Pure ? Qu'est-ce que pure dans ce contexte ? Merci par avance de votre réponse.

Salut,
Réponse dans le texte aussi. Je réponds à ton dernier mail et après je
relirai le premier pour voir si je peux apporter un autre angle que
Valentin, etc.
2013/12/20 Gautier DI FOLCO
2013/12/20 Gautier DI FOLCO
Nous avons pure :: Applicative f => a -> f a A2 : 'f a' signifie de type a implémentant le typeclass f (Applicative dans ce cas précis
Hmm pas vraiment. `f a` signifie de type `f a`, où `f` est un foncteur applicatif.
Donc pour 'f a', 'f' est un type paramétrique qui implémente le typeclass Applicative et qui a un paramètre 'a' ?
Oui tout à fait. 'f' peut être IO, [ ], Maybe, Either String (sans le 2è type en paramètre - oui c'est un début de réponse à ta seconde question) ... Ça signifie donc que les types n'ayant pas ou plusieurs paramètre de type
ne peuvent pas être utilisé dans cette fonction ? Comme Either ?
Bah comme je le dis justement entre parenthèses, pour Either, il y a un problème au niveau des "kinds" (grosso modo les type des types). Au vu de la gueule d'Applicative, 'f' ne doit prendre qu'un type en paramètre. Mais 'f' peut être un type paramétré à 45 paramètres, les 44 premiers étant fixés à des types précis ou pas forcément: data Blah a b c d = Blah instance Applicative (Blah a b c) where ... data Foo a b c = Foo instance Applicative (Foo Int String) where -- ça nécessite {-# LANGUAGE FlexibleInstances #-}, je peux t'expliquer pourquoi si ça t'intéresse. ... Et donc, si tu donnes un premier paramètre à Either et que tu laisses varier le second, tu obtiens un Applicative :-) C'est plus clair ? liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
A8 : idem mais avec deux arguments A9 : liftA2 (+) (Just 2) (Just 1) est équivalent à liftA (+) (Just 2) <*> Just 1 Q7 : Quel est l'intérêt du coup ?
Même réponse. Tu sembles chercher à éviter toute redondance. Pourquoi ne pas écrire tout ton code avec juste des lambdas et des applications ? :-)
En fait je cherche à savoir pourquoi il y a plusieurs formes, mais visiblement je me pose trop de questions :/
C'est pas commun de pouvoir écrire quelque chose de 15 manières différentes dès qu'on a un peu de "structure" (savoir que tel type implémente telle typeclass, tout ça). Desfois c'est du simple "sucre syntaxique", desfois c'est deux concepts qui coincident, desfois autre chose. Te prends pas trop la tête avec ça à mon avis.
Même une piste m'aiderais :/ J'ai l'impression qu'il s'agit de gérer soit une valeur soit une valeur et une fonction à lui appliquer, mais en quoi la valeur seule serait Pure ? Qu'est-ce que pure dans ce contexte ?
En gros, il s'agit de décorer des valeurs quelconques avec d'autres valeurs *qui sont dans un applicative". Ou vu dans l'autre sens, il s'agit de décorer un applicative avec des valeurs pures. Par "décorer" j'entends "mettre ensemble avec". L'exemple des erreurs dont parle Valentin est en fait assez simple si on arrive à se focaliser sur chaque truc un à un. Déjà, Lift:
data Lift f a = Pure a | Other (f a)
On va considérer Lift appliqué un 'f' donné, par exemple Maybe.
data LiftMaybe a = Pure a | Other (Maybe a)
Donc c'est comme si on avait collé tous les 'Maybe a' avec les 'a' tout court, en disant quand même qu'on se rappelle d'où chacun vient grâce aux constructeurs. Tu noteras que le constructeur "Pure" qui apparait par magie, il coincide *EXACTEMENT* avec la fonction "pure" de Applicative, appliquée pour 'f' = 'Lift':
instance Applicative (Lift f) where -- pure :: a -> Lift f a pure x = Pure x ...
Maintenant, pour l'histoire d'erreurs. Le foncteur "Constant" c'est :
data Constant a b = Constant { getConstant :: a }
Et son instance d'Applicative est un exercise assez simple et intéressant selon ta familiarité avec ces derniers. Dans tous les cas, t'as bien compris que le 'b' allait pas servir à grand chose. Autrement dit, ça prend deux types en paramètre, ça ignore le deuxième et stocke juste une valeur du premier type. Maintenant, Errors:
type Errors e = Lift (Constant e) -- équivalent à type Errors e a = Lift (Constant e) a -- à son tour "moralement équivalent à" : data Errors e a = Pure a | Other (Constant e)
'e' étant un type représentant une erreur (comme String ou une liste de String ou quoi). Maintenant, les instances pour Lift devraient t'aider à comprendre, en te disant que Errors rajoute aux valeurs "normales" (Pure, qui peut contenir un truc de n'importe quel type!) des valeurs d'erreur. Donc si avait t'avais des fonctions qui balançaient des erreurs à coups de 'error' et que maintenant tu veux faire ça plus joliement, il te suffit d'utiliser 'pure'/'Pure' pour balancer tes valeurs normales dans un 'Errors e a', et d'utiliser failure pour signaler une erreur. Essaye ensuite de voir comment tout cela se passe si tu fixes 'e' et 'a' sur des types précis, [String] et Int respectivement par exemple. Une dernière chose: un des gros gros trucs sympas avec les applicatives pour moi c'est de pouvoir écrire des trucs comme:
data Person = Person { name :: String, age :: Int, ip :: IPV6 }
Person <$> getNameFromNSALeak <*> getAgeFromFacebook <*> unsafeGetIp
où getNameFromNSALeak :: IO String getAgeFromFacebook :: IO Int unsafeGetIp :: IO IPV6 donc sont des trucs qui retournent des valeurs "dans un applicative" (ici IO, mais aussi parsers, Maybe, conteneurs, Async, et bien d'autres). N'hésite pas si certains passages ne sont pas clairs à demander des éclaircissements. -- Alp Mestanogullari

Bonjour Gautier, Personnellement, la lecture de ce papier http://strictlypositive.org/IdiomLite.pdf m'a aidé à mieux comprendre les foncteurs applicatifs. Il montre très bien que le principal intérêt de ces structures c'est de pouvoir écrire des fonctions plus génériques, comme à peu près toutes les structures en Haskell d'ailleurs... Une autre chose qui m'a beaucoup aidé c'est de comprendre le système de type de Haskell de manière plus détaillée. Tu peux voir du côté du livre de Benjamin Pierce, par exemple. Une des portes d'entrée possible, c'est de voir que le système de types est lui aussi fonctionnel : tu as des valeurs (les types) et des fonctions (les constructeurs de types), ces derniers pouvant être "currifiés". Et tu peux appliquer ces objets les uns aux autres selon leur "genre" (kind) qui est le "système de types" des types :-) Du coup une signature du genre:
pure :: (Applicative f) => a -> f a
se lit simplement et donne comme indication que 'f' doit être un
constructeur de types avec un argument (de genre '* -> * pour être précis.
Concernant tes questions, je crois que les autres réponses de la liste sont
claires et que l'article dont je t'ai donné le lien t'aidera aussi. Juste
une remarque sur Q1 : il n'est question d'intérêt ici, mais d'une
obligation "contractuelle" qui rend nécessaire que 'f' soit bien un
foncteur pour permettre de construire un applicatif, simplement parce qu'un
foncteur est la brique de base permettant d'encapsuler des valeurs dans
types "conteneurs".
Cordialement,
--
Arnaud Bailly
FoldLabs Associate: http://foldlabs.com
2013/12/20 Gautier DI FOLCO
Bonjour,
Cela fait maintenant quelques mois que je côtoie Haskell, j'arrive presque toujours à mes fins, mais cela ne me convient pas, je tente donc de prendre le problème à sa racine : la compréhension, ou plutôt l'assimilation des concepts. Je pense que j'ai un soucis à ce niveau car je pense plutôt bien voir comment tout s'emboîte, mais face au code, je suis incapable de l'appliquer ! Je vous laisse juger par vous-même. Je vais tenter d'expliquer ma manière de voir les choses de manière aussi détaillée que possible via un ensemble d'assertions (notées AN où N est le numéro de l'assertion). Le typeclass Applicative à la tête suivante : class Functor f => Applicative f where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b (*>) :: f a -> f b -> f b (<*) :: f a -> f b -> f a
A1 : Ce qui signifie que tout type implémentant ces fonctions doivent également implémenter les fonctions de Functor. Q1 : quelle est l'intérêt ?
Nous avons pure :: Applicative f => a -> f a A2 : 'f a' signifie de type a implémentant le typeclass f (Applicative dans ce cas précis A3 : Il s'agit "simplement" d'un lifting, d'une encapsulation, grosso-modo de donner un paramètre à un constructeur.
Ensuite (<*>) :: Applicative f => f (a -> b) -> f a -> f b A4 : prend une fonction et un paramètre dans un "emballage" implémentant Applicative et applique cette valeur à cette fonction en retournant le type dans un autre "emballage". A5 : cette fonction sert dans deux cas : à fournir un moyen de manipuler le contenu de "boîtes", "décurryfier" dans le sens passer des arguments au fur et à mesure à une fonction. Q2 : Y a-t-il d'autres cas d'application.
A6 : <* et *> ne servent qu'à appliquer un des arguments. Q3 : quels sont les cas d'applications typiques ? j'ai du mal à voir
Ensuite il y a les fonctions de lifting :
liftA :: Applicative f => (a -> b) -> f a -> f b A7 : prend une fonction "normale" à un argument et en fait une fonction maniant une structure encapsulée. Q4 : quel est l'intérêt ? liftA (+1) $ Just 1 n'est pas égale à pure (+1) <*> Just 1 ? Q5 : ou à la version Functor : (+1) <$> Just 1 ? Q6 : dans le cas de version Functor, quel est l'intérêt de la passer Applicative ? liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c A8 : idem mais avec deux arguments A9 : liftA2 (+) (Just 2) (Just 1) est équivalent à liftA (+) (Just 2) <*> Just 1 Q7 : Quel est l'intérêt du coup ? liftA3 :: Applicative f => (a -> b -> c -> d) -> f a -> f b -> f c -> f d A10 : idem mais avec trois arguments
Q8 : Quel est l'intérêt de Control.Applicative.Lift ? Il est écrit : "Adding a new kind of pure computation to an applicative functor" Q9 : J'ai du mal à saisir cette notion de calcul, est-ce qu'il est possible de me l'expliquer en deux mots ?
Je me rend compte que mon mail est très long, ça fait plus de deux mois que je me casse les dents dessus, j'ai écumé plusieurs articles/livres, pas moyen de me faire une idée claire de tout ça, ou de voir les applications pratiques. Je vous serait très reconnaissant de prendre le temps de me répondre et/ou de valider/invalider mes assertions, Merci par avance.
PS : Par la suite j'aurais des question sur Alternative et les Monades PPS : la longueur de la rédaction m'a donné une idée pour Q7 : A11 : liftA2 et liftA3 servent à obtenir des fonctions "passables" à des fonctions qui le demande, ex : myF :: (Maybe a -> Maybe b -> Maybe c) -> a -> b -> Maybe c myF f a b = f (Just a) (Just b) la fonction serait appelable via myF (liftA2 (+)) 1 2
_______________________________________________ Haskell-fr mailing list Haskell-fr@haskell.org http://www.haskell.org/mailman/listinfo/haskell-fr

Bonjour,
Merci de vos réponses, la suite dans le texte :
Le 21 décembre 2013 02:57, Alp Mestanogullari
Ça signifie donc que les types n'ayant pas ou plusieurs paramètre de type
ne peuvent pas être utilisé dans cette fonction ? Comme Either ?
Bah comme je le dis justement entre parenthèses, pour Either, il y a un problème au niveau des "kinds" (grosso modo les type des types). Au vu de la gueule d'Applicative, 'f' ne doit prendre qu'un type en paramètre. Mais 'f' peut être un type paramétré à 45 paramètres, les 44 premiers étant fixés à des types précis ou pas forcément:
Une sorte de fonction curryfiée à laquelle il manquerait un paramètre ?
data Blah a b c d = Blah
instance Applicative (Blah a b c) where ...
data Foo a b c = Foo
instance Applicative (Foo Int String) where -- ça nécessite {-# LANGUAGE FlexibleInstances #-}, je peux t'expliquer pourquoi si ça t'intéresse. ...
Je veux bien.
Et donc, si tu donnes un premier paramètre à Either et que tu laisses varier le second, tu obtiens un Applicative :-)
C'est plus clair ?
Oui
L'exemple des erreurs dont parle Valentin est en fait assez simple si on arrive à se focaliser sur chaque truc un à un.
Déjà, Lift:
data Lift f a = Pure a | Other (f a)
On va considérer Lift appliqué un 'f' donné, par exemple Maybe.
data LiftMaybe a = Pure a | Other (Maybe a)
Donc c'est comme si on avait collé tous les 'Maybe a' avec les 'a' tout court, en disant quand même qu'on se rappelle d'où chacun vient grâce aux constructeurs. Tu noteras que le constructeur "Pure" qui apparait par magie, il coincide *EXACTEMENT* avec la fonction "pure" de Applicative, appliquée pour 'f' = 'Lift':
instance Applicative (Lift f) where -- pure :: a -> Lift f a pure x = Pure x ...
Maintenant, pour l'histoire d'erreurs. Le foncteur "Constant" c'est :
data Constant a b = Constant { getConstant :: a }
Et son instance d'Applicative est un exercise assez simple et intéressant selon ta familiarité avec ces derniers. Dans tous les cas, t'as bien compris que le 'b' allait pas servir à grand chose.
Autrement dit, ça prend deux types en paramètre, ça ignore le deuxième et stocke juste une valeur du premier type. Maintenant, Errors:
type Errors e = Lift (Constant e) -- équivalent à type Errors e a = Lift (Constant e) a -- à son tour "moralement équivalent à" : data Errors e a = Pure a | Other (Constant e)
'e' étant un type représentant une erreur (comme String ou une liste de String ou quoi).
Maintenant, les instances pour Lift devraient t'aider à comprendre, en te disant que Errors rajoute aux valeurs "normales" (Pure, qui peut contenir un truc de n'importe quel type!) des valeurs d'erreur. Donc si avait t'avais des fonctions qui balançaient des erreurs à coups de 'error' et que maintenant tu veux faire ça plus joliement, il te suffit d'utiliser 'pure'/'Pure' pour balancer tes valeurs normales dans un 'Errors e a', et d'utiliser failure pour signaler une erreur. Essaye ensuite de voir comment tout cela se passe si tu fixes 'e' et 'a' sur des types précis, [String] et Int respectivement par exemple.
Je vois mieux, mais du coup, quel est l'avantage par rapport à un Either ?
Le 21 décembre 2013 08:13, Arnaud Bailly
Bonjour Gautier,
Personnellement, la lecture de ce papier http://strictlypositive.org/IdiomLite.pdf m'a aidé à mieux comprendre les foncteurs applicatifs. Il montre très bien que le principal intérêt de ces structures c'est de pouvoir écrire des fonctions plus génériques, comme à peu près toutes les structures en Haskell d'ailleurs...
En fait mon soucis c'est de trouver comment l'appliquer au-delà des structures "triviale" (IO, Maybe, List, etc.) Une autre chose qui m'a beaucoup aidé c'est de comprendre le système de
type de Haskell de manière plus détaillée. Tu peux voir du côté du livre de Benjamin Pierce, par exemple. Une des portes d'entrée possible, c'est de voir que le système de types est lui aussi fonctionnel : tu as des valeurs (les types) et des fonctions (les constructeurs de types), ces derniers pouvant être "currifiés". Et tu peux appliquer ces objets les uns aux autres selon leur "genre" (kind) qui est le "système de types" des types :-)
Lequel de ses livres ? Quand je tombe sur quelque chose comme :
instance Applicative (Lift f) where
Cela signifie-t-il "pour tout type Lift encapsulant un type quelconque 'f', je définis les fonction de son typeclass Applicative" ? Du coup le Alternative sont un sorte de Monoïde pour les Applicative ? Il faut qu'il y en ai un de bon sur un couple ou un ensemble, c'est ça ? Ça sert à définir une valeur par défaut, si je comprends. Merci par avance pour vos réponses (on touche au but).

Bonjour,
2013/12/21 Gautier DI FOLCO
Une sorte de fonction curryfiée à laquelle il manquerait un paramètre ?
C'est exactement ça, simplement ça se passe au niveau des types oui. Le dernier paramètre n'est pas "donné" au type, on laisse ça en suspens en quelque sorte.
instance Applicative (Foo Int String) where -- ça nécessite {-# LANGUAGE
FlexibleInstances #-}, je peux t'expliquer pourquoi si ça t'intéresse. ...
Je veux bien.
Alors, si on essaie d'écrire cette instance sans activer cette extension, normalement on se choppe quelque chose du genre dans la tête: Illegal instance declaration for `Applicative (Blah Int String Char)' (All instance types must be of the form (T a1 ... an) where a1 ... an are *distinct type variables*, and each type variable appears at most once in the instance head. Use -XFlexibleInstances if you want to disable this.) Ca veut dire que par défaut, quand tu définis une instance t'es pas vraiment libre, ça doit être de la forme qu'ils donnent: "T a1 ... an", i.e:
instance MaTypeClass (T a1 ... an) where
où T est le nom d'un type ("type constructor"), et les "ai" sont des type variables. Ca veut dire que tu n'as pas vraiment le droit de fixer *un* des types (encore moins plusieurs). Tu as le même genre de restrictions sur le contexte (dans "instance (Blah a, Foo b) => MaClasse (Either a b) where", le contexte ce sont les deux contraintes à gauche de "=>"), qui ont donné lieu à l'extension "FlexibleContexts". Voilà pour la petite disgression. Je vois mieux, mais du coup, quel est l'avantage par rapport à un Either ?
Bah Errors là, c'est équivalent à Either a (Constant e) :
type Errors e a = Pure a | Other (Constant e)
type Either a b = Left a | Right b
donc on voit que ça marche si on prend 'b = Constant e'. Donc Errors est un cas particulier de Either en fait tout simplement. Il n'y a pas énormément d'avantages à utiliser Errors là plutot que Either, ErrorT & compagnie, parce qu'il y a pleins de fonctions utilitaires pour les autres, et puis de toute façon y'a 150 façons de gérer les erreurs en haskell et chacun à la sienne ou suit celle d'un gourou qu'il aime bien.
En fait mon soucis c'est de trouver comment l'appliquer au-delà des structures "triviale" (IO, Maybe, List, etc.)
Essaye de googler "applicative parsers" ça peut être intéressant. 'aeson', 'attoparsec', 'parsec' etc illustrent tous cela il me semble. Mais sinon te focalise pas trop dessus, avance, écris du code, continue à apprendre des choses, ça fera vraiment "clic" le moment venu ne t'inquiètes pas. Il y a encore quelques trucs que après 5 ans de Haskell j'ai encore du mal à comprendre, c'est un peu ce qui fait son charme d'ailleurs mais bon.
Lequel de ses livres ?
"Types and Programming Languages" je suppose (affectueusement appelé 'TAPL").
Quand je tombe sur quelque chose comme :
instance Applicative (Lift f) where
Cela signifie-t-il "pour tout type Lift encapsulant un type quelconque 'f', je définis les fonction de son typeclass Applicative" ?
Applicative représente la possibilité de pouvoir effectuer certaines opérations, un peu comme tu pourrais avoir une "Interface" (au sens OO) en Java. Une instance se lit souvent comme "voilà comment on définit ces opérations pour mon type". En l'occurence, dans ton exemple, on définit une infinité d'instances, en qq sorte, puisqu'on laisse varier 'f'. Du coup le Alternative sont un sorte de Monoïde pour les Applicative ?
Il faut qu'il y en ai un de bon sur un couple ou un ensemble, c'est ça ? Ça sert à définir une valeur par défaut, si je comprends.
Alternative fournit d'une part une "valeur par défaut" en effet ('empty'), et une façon d'exprimer une sorte de "combinaison", l'opérateur <|>, qui souvent agit comme une sorte de "ou". Par exemple, l'exemple des Alternatives pour les parsers: si tu as un code du genre :
parseA <|> parseB
typiquement il essayera de parser un "A" et si ça échoue, ça essayera de parser un "B". -- Alp Mestanogullari

Le 21 décembre 2013 16:56, Alp Mestanogullari
Bonjour,
2013/12/21 Gautier DI FOLCO
Une sorte de fonction curryfiée à laquelle il manquerait un paramètre ?
C'est exactement ça, simplement ça se passe au niveau des types oui. Le dernier paramètre n'est pas "donné" au type, on laisse ça en suspens en quelque sorte.
instance Applicative (Foo Int String) where -- ça nécessite {-#
LANGUAGE FlexibleInstances #-}, je peux t'expliquer pourquoi si ça t'intéresse. ...
Je veux bien.
Alors, si on essaie d'écrire cette instance sans activer cette extension, normalement on se choppe quelque chose du genre dans la tête:
Illegal instance declaration for `Applicative (Blah Int String Char)' (All instance types must be of the form (T a1 ... an) where a1 ... an are *distinct type variables*, and each type variable appears at most once in the instance head. Use -XFlexibleInstances if you want to disable this.)
Ca veut dire que par défaut, quand tu définis une instance t'es pas vraiment libre, ça doit être de la forme qu'ils donnent: "T a1 ... an", i.e:
instance MaTypeClass (T a1 ... an) where
où T est le nom d'un type ("type constructor"), et les "ai" sont des type variables. Ca veut dire que tu n'as pas vraiment le droit de fixer *un* des types (encore moins plusieurs). Tu as le même genre de restrictions sur le contexte (dans "instance (Blah a, Foo b) => MaClasse (Either a b) where", le contexte ce sont les deux contraintes à gauche de "=>"), qui ont donné lieu à l'extension "FlexibleContexts".
Voilà pour la petite disgression.
Je vois mieux, mais du coup, quel est l'avantage par rapport à un Either ?
Bah Errors là, c'est équivalent à Either a (Constant e) :
type Errors e a = Pure a | Other (Constant e)
type Either a b = Left a | Right b
donc on voit que ça marche si on prend 'b = Constant e'. Donc Errors est un cas particulier de Either en fait tout simplement. Il n'y a pas énormément d'avantages à utiliser Errors là plutot que Either, ErrorT & compagnie, parce qu'il y a pleins de fonctions utilitaires pour les autres, et puis de toute façon y'a 150 façons de gérer les erreurs en haskell et chacun à la sienne ou suit celle d'un gourou qu'il aime bien.
En fait mon soucis c'est de trouver comment l'appliquer au-delà des structures "triviale" (IO, Maybe, List, etc.)
Essaye de googler "applicative parsers" ça peut être intéressant. 'aeson', 'attoparsec', 'parsec' etc illustrent tous cela il me semble. Mais sinon te focalise pas trop dessus, avance, écris du code, continue à apprendre des choses, ça fera vraiment "clic" le moment venu ne t'inquiètes pas. Il y a encore quelques trucs que après 5 ans de Haskell j'ai encore du mal à comprendre, c'est un peu ce qui fait son charme d'ailleurs mais bon.
Lequel de ses livres ?
"Types and Programming Languages" je suppose (affectueusement appelé 'TAPL").
Quand je tombe sur quelque chose comme :
instance Applicative (Lift f) where
Cela signifie-t-il "pour tout type Lift encapsulant un type quelconque 'f', je définis les fonction de son typeclass Applicative" ?
Applicative représente la possibilité de pouvoir effectuer certaines opérations, un peu comme tu pourrais avoir une "Interface" (au sens OO) en Java. Une instance se lit souvent comme "voilà comment on définit ces opérations pour mon type". En l'occurence, dans ton exemple, on définit une infinité d'instances, en qq sorte, puisqu'on laisse varier 'f'.
Du coup le Alternative sont un sorte de Monoïde pour les Applicative ?
Il faut qu'il y en ai un de bon sur un couple ou un ensemble, c'est ça ? Ça sert à définir une valeur par défaut, si je comprends.
Alternative fournit d'une part une "valeur par défaut" en effet ('empty'), et une façon d'exprimer une sorte de "combinaison", l'opérateur <|>, qui souvent agit comme une sorte de "ou". Par exemple, l'exemple des Alternatives pour les parsers: si tu as un code du genre :
parseA <|> parseB
typiquement il essayera de parser un "A" et si ça échoue, ça essayera de parser un "B".
-- Alp Mestanogullari
_______________________________________________ Haskell-fr mailing list Haskell-fr@haskell.org http://www.haskell.org/mailman/listinfo/haskell-fr
Ok, merci c'est plus claire.
participants (4)
-
Alp Mestanogullari
-
Arnaud Bailly
-
Gautier DI FOLCO
-
Valentin Robert