• Aucun résultat trouvé

Tout comme les foncteurs applicatifs, et les foncteurs avant eux, les monades viennent avec quelques lois que toutes les instances de Monad doivent respecter. Être simplementMonad une instance de Monad ne fait pas automatiquement une monade, cela signifieMonad

simplement qu’on a créé une instance. Un type est réellement une monade si les lois des monades sont respectées. Ces lois nous permettent d’avoir des suppositions raisonnables sur le type et son comportement.

Haskell permet à n’importe quel type d’être une instance de n’importe quelle classe de types tant que les types correspondent. Il ne peut pas vérifier si les lois sont respectées,

vis des lois des monades. On peut faire confiance aux types de la bibliothèque standard pour satisfaire ces lois, mais plus tard, lorsque l’on écrira nos propres monades, il faudra vérifier manuellement que les lois tiennent. Mais ne vous inquiétez pas, elles ne sont pas compliquées.

Composition à gauche par return

La première loi dit que si l’on prend une valeur, qu’on la place dans un contexte par défaut via returnreturn et qu’on la donne à une fonction avec >>=

>>= , c’est la même chose que de donner directement la valeur à la fonction. Plus formellement : return x >>= f est égal à f x

Si vous regardez les valeurs monadiques comme des valeurs avec un contexte, et returnreturn comme prenant une valeur et la plaçant dans un contexte minimal qui présente toujours cette valeur, c’est logique, parce que si ce contexte est réellement minimal, alors donner la valeur monadique à la fonction ne devrait pas être différent de l’application de la fonction à la valeur normale, et c’est en effet le cas.

Pour la monade MaybeMaybe , returnreturn est définie comme JustJust . La monade MaybeMaybe se préoccupe des échecs possibles, et si l’on a une valeur qu’on met dans un tel contexte, c’est logique de la traiter comme un calcul réussi, puisque l’on connaît sa valeur. Voici returnreturn utilisé avec MaybeMaybe :

ghci> return 3 >>= (\x -> Just (x+100000)) Just 100003

ghci> (\x -> Just (x+100000)) 3

Just 100003

Pour la monade des listes, return place une valeur dans une liste singleton. L’implémentation de >>=return >>= prend chaque valeur de la liste et applique la fonction dessus, mais puisqu’il n’y a qu’une valeur dans une liste singleton, c’est la même chose que d’appliquer la fonction à la valeur :

ghci> return "WoM" >>= (\x -> [x,x,x]) ["WoM","WoM","WoM"]

ghci> (\x -> [x,x,x]) "WoM"

["WoM","WoM","WoM"]

On a dit que pour IO , returnIO return retournait une action I/O qui n’avait pas d’effet de bord mais retournait la valeur en résultat. Il est logique que cette loi tienne ainsi pour IO également.IO

Composition à droite par return

La deuxième loi dit que si l’on a une valeur monadique et qu’on utilise >>=>>= pour la donner à returnreturn , alors le résultat est la valeur monadique originale. Formellement :

m >>= return est égal à m

Celle-ci peut être un peu moins évidente que la précédente, mais regardons pourquoi elle doit être respectée. Lorsqu’on donne une valeur monadique à une fonction avec >>= , cette fonction prend une valeur normale et retourne une valeur monadique. return>>= return est une telle fonction, si vous considérez son type. Comme on l’a dit, return place une valeur dans un contexte minimal qui présente cette valeur en résultat. Cela signifie,return par exemple, pour MaybeMaybe , qu’elle n’introduit pas d’échec, et pour les listes, qu’elle n’ajoute pas de non déterminisme. Voici un essai sur quelques monades :

ghci> Just "move on up" >>= (\x -> return x) Just "move on up"

ghci> [1,2,3,4] >>= (\x -> return x) [1,2,3,4]

ghci> putStrLn "Wah!" >>= (\x -> return x) Wah!

Si l’on regarde de plus près l’exemple des listes, l’implémentation de >>=>>= est :

xs >>= f = concat (map f xs)

Donc, quand on donne [1, 2, 3, 4] à return[1, 2, 3, 4] return , d’abord returnreturn est mappée sur [1, 2, 3, 4][1, 2, 3, 4] , résultant en [[1],[2],[3],[4]][[1],[2],[3],[4]] et ensuite ceci est concaténé et retourne notre liste originale.

Les lois de composition à gauche et à droite par returnreturn décrivent simplement comment returnreturn doit se comporter. C’est une fonction importante pour faire des valeurs monadiques à partir de valeurs normales, et ce ne serait pas bon que les valeurs qu’elle produit fassent des tas d’autres choses.

Associativité

La dernière loi des monades dit que quand on a une chaîne d’application de fonctions monadiques avec >>=>>= , l’ordre d’imbrication ne doit pas importer. Formellement énoncé :

(m >>= f) >>= g est egal à m >>= (\x -> f x >>= g)

Hmmm, que se passe-t-il donc là ? On a une valeur monadique m , et deux fonctions monadiques fm f et gg . Quand on fait (m >>= f) >>= g(m >>= f) >>= g , on donne mm à ff , ce qui résulte en une valeur monadique. On donne ensuite cette valeur monadique à gg . Dans l’expression

m >>= (\x -> f x >>= g)

m >>= (\x -> f x >>= g) , on prend une valeur monadique et on la donne à une fonction qui donne le résultat de f xf x à gg . Il n’est pas facile de voir que les deux sont égales, regardons donc un exemple qui rend cette égalité un peu plus claire.

Vous souvenez-vous de notre funambule Pierre qui marchait sur une corde tendue pendant que des oiseaux atterrissaient sur sa perche ? Pour simuler les oiseaux atterrissant sur sa perche, on avait chaîné plusieurs fonctions qui pouvaient mener à l’échec :

ghci> return (0,0) >>= landRight 2 >>= landLeft 2 >>= landRight 2

Just (2,4)

On commençait avec Just (0, 0) et on liait cette valeur à la prochaine fonction monadique, landRight 2Just (0, 0) landRight 2 . Le résultat était une nouvelle valeur monadique qui était liée dans la prochaine fonction monadique, et ainsi de suite. Si nous parenthésions explicitement ceci, cela serait :

ghci> ((return (0,0) >>= landRight 2) >>= landLeft 2) >>= landRight 2

Just (2,4)

Mais on peut aussi écrire la routine ainsi :

return (0,0) >>= (\x ->

landRight 2 x >>= (\y ->

landLeft 2 y >>= (\z ->

landRight 2 z))) return (0, 0)

return (0, 0) est identique à Just (0, 0)Just (0, 0) et quand on le donne à la lambda, xx devient (0, 0)(0, 0) . landRightlandRight prend un nombre d’oiseaux et une perche (une paire de nombres) et c’est ce qu’elle reçoit. Cela résulte en Just (0, 2)Just (0, 2) et quand on donne ceci à la prochaine lambda, y est égal à (0, 2)y (0, 2) . Cela continue jusqu’à ce que le dernier oiseau se pose, produisant Just (2, 4)Just (2, 4) , qui est effectivement le résultat de l’expression entière.

Ainsi, il n’importe pas de savoir comment vous imbriquez les appels de fonctions monadiques, ce qui importe c’est leur sens. Voici une autre façon de regarder cette loi : considérez la composition de deux fonctions, ff et gg . Composer deux fonctions se fait ainsi :

(.) :: (b -> c) -> (a -> b) -> (a -> c)

f . g = (\x -> f (g x))

Si le type de gg est a -> ba -> b et le type de ff est b -> cb -> c , on peut les arranger en une nouvelle fonction ayant pour type a -> ca -> c , de façon à ce que le paramètre soit passé entre les deux fonctions. Et si ces deux fonctions étaient monadiques, et retournait des valeurs monadiques ? Si l’on avait une fonction de type a -> m ba -> m b , on ne pourrait pas simplement passer son résultat à une fonction de type b -> m cb -> m c , parce que la fonction attend un bb normal, pas un monadique. On pourrait cependant utiliser >>=>>= pour réaliser cela. Ainsi, en utilisant >>=>>= , on peut composer deux fonctions monadiques :

(<=<) :: (Monad m) => (b -> m c) -> (a -> m b) -> (a -> m c)

f <=< g = (\x -> g x >>= f)

ghci> let f x = [x,-x]

ghci> let g x = [x*3,x*2]

ghci> let h = f <=< g

ghci> h 3

[9,-9,6,-6]

Cool. Qu’est-ce que cela a à voir avec la loi d’associativité ? Eh bien, quand on regarde la loi comme une loi de composition, elle dit que

f <=< (g <=< h) doit être égal à (f <=< g) <=< h. C’est juste une autre façon de dire que l’imbrication des opérations n’importe pas pour les monades.

Si l’on traduit les deux premières lois avec <=< , la composition de return<=< return à gauche dit que pour toute fonction monadique ff , f <=< return est égal à f et celle à droite dit que return <=< f est aussi égal à f.

Dans ce chapitre, on a vu quelques bases des monades et appris comment la monade MaybeMaybe et la monade des listes fonctionnent. Dans le prochain chapitre, nous regarderons tout un tas d’autre monades cool, et on apprendra à créer les nôtres.

Et pour quelques monades de