
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