[Haskell-fr] Éclaircissements sur les monades

Gautier DI FOLCO gautier.difolco at gmail.com
Wed Mar 26 21:59:15 UTC 2014


Le 26 mars 2014 19:58, Alp Mestanogullari <alpmestan at gmail.com> a écrit :

> Ok, je vais essayer de reprendre plus tranquillou ce que j'ai dit en
> répondant point par point.
>
> 2014-03-26 19:32 GMT+01:00 Gautier DI FOLCO <gautier.difolco at gmail.com>:
>
> Le 26 mars 2014 17:55, Alp Mestanogullari <alpmestan at gmail.com> a écrit :
>>
>> Le truc du contexte, je pense qu'ya une bonne image pour le représenter.
>>>
>>> Ce qui est propre au monade et que n'ont pas les applicatives, c'est que
>>> la suite d'une fonction dépende de ce qui se passe à un moment donné.
>>> Exemple:
>>>
>>> foo :: IO ()
>>> foo = do
>>>   s <- getLine
>>>   case s of
>>>       "hello" -> putStrLn " world!"
>>>       _        -> foo
>>>
>>> ici, on fait quelque chose dans IO, et selon le résultat, il peut se
>>> passer deux choses complètement différentes. On se sert beucoup de ce genre
>>> de "contexte" dans les parsers par exemple, c'est littéralement explicit,
>>> les parsers ont une forte analogie avec la monade State.
>>>
>>
>> En gros tu crée une dépendance qui force ton exécution dans un certain
>> ordre en lui passant le précédent monde ?
>>
>
> Bah, tu as tout simplement le moyen de faire ça avec ce que te donne IO.
> Mais tu pourrais faire pareil dans une autre monade.
> L'idée c'est que là, 'getLine' c'est de type IO String, donc 's' est une
> String, on est d'accord? Hé bien on peut grâce à la do-notation (c'est
> grâce à la façon dont la do-notation est traduite en avalanche de >>= et
> return & compagnie en fait). Comment?
>
> Très simple:
>
> f :: IO ()
> f = do
>   s <- getLine
>   putStrLn s
>
> se traduit en
>
> f' :: IO
> f' = getLine >>= \s -> putStrLn s
>
> -- ou plus simple: f' = getLine >>= putStrLn
>

Je viens de piger je crois.
En fait je me mélangeais dans le sésucrage, c'est plus claire déjà.


>  Re-regardons le type de >>= pour IO:
>
> (>>=) :: IO a -> (a -> IO b) -> IO b
>
> En gros t'as qqch qui va te produire un 'a' à travers des effets (lecture
> de fichiers, depuis une DB, depuis stdin, ...) - (c'est ça la signification
> de 'IO a') - et tu vois que le deuxième argument de >>= prend un 'a', donc
> en gros >>= donne à son deuxième argument la valeur générée par le premier
> argument. C'est cette "inspection" qui te permet en gros de faire un peu ce
> que tu veux avec ce 'a' tant que tu produis un 'IO b' pour un certain type
> 'b'.
>

ça c'était bon (je suis fier de moi alors que c'est "tout bête")


>  Alors qu'avec Applicative, tu peux avoir des "effets", des computations
>>> avec un petit truc en plus (parseurs qui retournent une valeur mais qui
>>> entre temps ont consumé une partie de l'input pour produire ces valeurs),
>>> etc, mais t'es obligé de garder la même "structure" pour ton code, ex:
>>>
>>
>> Tu entends quoi par "computation" (désolé, je bloque sur ce terme, ça ne
>> rentre pas :/)
>>
>
> l'évaluation d'une expression, de qqch.
>

pas mieux, ça correspond à quoi ?


Tu veux dire truc en moins ou Monade ? si non, je sur largué.
>>
>
> Non non, je veux dire que Monad permet ça aussi, mais Applicative ne
> permet *QUE* ça, alors que Monad, comme montré juste au-dessus, permet
> d'inspecter ce que t'as produit et réagir en conséquence. Applicative te
> permet juste d'appliquer des fonctions pures, par ex de type 'a -> b -> c',
> à des arguments qui viennent "d'ailleurs", par ex 'IO a' et 'IO b'.
>
> Exemple:
>
> data ProgrammingLanguage = ProgrammingLanguage { name :: String,
> fileExtension :: String }
>
> getProgrammingLanguageFromStdin :: IO ProgrammingLanguage
> getProgrammingLanguageFromStdin =
>     ProgrammingLanguage <$> getLine
>                                       <*> getLine
>
> Ca va te permettre d'entrer le nom d'un langage, <ENTER>, l'extension des
> fichiers associée à ce langage (".hs" pour Haskell par exemple, quoi), et
> ça te retournera une valeur de type ProgrammingLanguage.
>
> Si tu utilisais les fonctions de Monad pour faire ça:
>
> getProgrammingLanguageFromStdin :: IO ProgrammingLanguage
> getProgrammingLanguageFromStdin = do
>     name  <- getLine
>     fileExt <- getLine
>     return $ ProgrammingLanguage name fileExt
>
> Tu noteras que contrairement à mon exemple à la con de 'case s of "hello"
> -> ...', ici on cherche pas à agir différemment selon la valeur qu'on
> obtient grâce à getLine, que ce soit pour le nom où l'extension, et c'est
> exactement ce qui fait qu'on peut écrire cette fonction tout à fait utile
> aussi bien avec les fonctions d'Applicative ( <*> combinée à <$> = fmap de
> Functor ) qu'avec la do-notation.
>

ok pigé, je ne voyais pas où tu voulais en venir avant (je pensais que tu
parlais de faire des IO via des Applicatives).


> Histoire d'être complet, si tu veux écrire ça sans le do mais quand même
> avec les fonctions de Monad:
>
> getProgrammingLanguageFromStdin :: IO ProgrammingLanguage
> getProgrammingLanguageFromStdin =
>     getLine >>= \name -> getLine >>= \fileExt -> return
> (ProgrammingLanguage name fileExt)
>
>
>> Perdu :/
>> Quelle est la "déclaration" (je ne sais pas comment ça se dit :
>> data/type/newtype) de Parser ?
>>
>
> Peu importe, mais il est établi que les parseurs sont Monad/Applicative.
> "Parser Int" c'est qqch qui va essayer de parser un int quand tu lui
> donneras une String sur laquelle essayer.
>
> Parser Person c'est un truc qui va essayer de parser une "Person" quand tu
> lui donneras une entrée sur laquelle essayer. Comment? Il va essayer de
> parser un Int, puis une String, et si les deux réussissent, il passe tout
> ça au constructeur 'Person'.
>
> Comment le monde est "passé" aux parse* ?
>>
>
> Ca fait partie du fonctionnement interne. Généralement un parseur c'est
> une fonction du genre 'String -> (a, String)'. Donc un truc qui attend une
> String en entrée et qui va tenter de produire un 'a' (Person par exemple
> dans notre exemple) grâce à ce qu'il trouvera dans cette String et
> retournera à côté de la valeur "ce qui n'a pas encore été consommé". Dans
> la vraie vie, ils sont plus compliqués, mais ça te donne peut-être une idée.
>

ok, compris.


>  Comment il sait quel type de Parser il doit produire ? (le première
>> ligne du corps de blah appel le Constructeur du type Parser, comment
>> sait-il qu'il va "retourner" une valeur de type Parser Person ? vu que
>> Person n'est indiqué nul part dans le corps).
>>
>
> Il le sait parce que il connait le type des attributs de Person (age de
> type Int, name de type String) et il connait le type de parseInt et
> parseString.
> Et si, justement, Person est indiqué dans le corps. La version Monad
> t'aidera peut-être:
>
> parsePerson :: Parser Person
> parsePerson = do
>     age    <- parseInt
>     name <- parseString
>     return (Person age name)
>
> La version applicative fait *EXACTEMENT* ça.
>

ok, je comprends.


> Ne t'inquiètes pas, on est tous passés par là, faut changer sa façon de
> penser pour bien piger tout ça. Faut oublier toutes les merdes qu'on a
> appris ailleurs, repenser la programmation. Ca va prendre un peu de temps
> mais tu vas y arriver.
>
> En attendant, hésite pas à signaler ce qui n'est toujours pas clair après
> ce message.
>

Merci, merci encore, c'est ça que j'aime bien en Haskell, dès que tu as
compris c'est limpide mais tu as toujours des choses à apprendre.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://www.haskell.org/pipermail/haskell-fr/attachments/20140326/4b76e857/attachment-0001.html>


More information about the Haskell-fr mailing list