[Haskell-fr] Éclaircissements sur les monades

Alp Mestanogullari alpmestan at gmail.com
Wed Mar 26 18:58:12 UTC 2014


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

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'.


>  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.


> 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.

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.


> 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.

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.

-- 
Alp Mestanogullari
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://www.haskell.org/pipermail/haskell-fr/attachments/20140326/f8fa5e39/attachment-0001.html>


More information about the Haskell-fr mailing list