• Aucun résultat trouvé

Alors que nous débutions notre périple vers le sommet du Mont Monade, on a d’abord vu des foncteurs, qui étaient pour les choses sur lesquelles on pouvait mapper. Puis, on a appris qu’il existait des foncteurs améliorés appelés foncteurs applicatifs, qui nous permettaient d’appliquer des fonctions normales sur plusieurs valeurs applicatives ainsi que de prendre une valeur normale et de la placer dans un contexte par défaut. Finalement, on a introduit les monades comme des foncteurs applicatifs améliorés, qui ajoutaient la possibilité de donner ces valeurs avec des contextes à des fonctions normales.

Ainsi, chaque monade est un foncteur applicatif, et chaque foncteur applicatif est un foncteur. La classe de types Applicative a une contrainte de classe telle que notre type doit être une instance de Functor avant de pouvoir devenir une instance d’ Applicative. Mais bien que

Monad devrait avoir la même contrainte de classe pour Applicative , puisque chaque monade est un foncteur applicatif, elle ne l’a pas, parce que la classe de types Monad a été introduite en Haskell longtemps avant Applicative.

Mais bien que chaque monade soit un foncteur, on n’a pas besoin que son type soit une instance de Functor grâce à la fonction liftM. liftM prend une fonction et une valeur monadique et mappe la fonction sur la valeur. C’est donc comme fmap ! Voici le type de liftM :

liftM :: (Monad m) => (a -> b) -> m a -> m b

Et le type de fmap :

fmap :: (Functor f) => (a -> b) -> f a -> f b

Si un type est à la fois instance de Functor et de Monad et obéit leurs lois respectives, alors ces deux fonctions doivent être identiques (c’est le cas pour toutes les monades qu’on a vues jusqu’ici). Un peu comme pure et return font la même chose, seulement que la première a une contrainte de classe Applicative alors que l’autre a une contrainte Monad. Testons liftM :

ghci> liftM (*3) (Just 8) Just 24

ghci> fmap (*3) (Just 8) Just 24

ghci> runWriter $ liftM not $ Writer (True, "chickpeas") (False,"chickpeas")

ghci> runWriter $ fmap not $ Writer (True, "chickpeas") (False,"chickpeas")

ghci> runState (liftM (+100) pop) [1,2,3,4] (101,[2,3,4])

(101,[2,3,4])

On sait déjà comment fmap fonctionne avec les valeurs Maybe. Et liftM est identique. Pour les valeurs Writer, la fonction est mappée sur la première composante du tuple, qui est le résultat. Faire fmap ou liftM sur un calcul à états résulte en un autre calcul à états, mais son résultat final sera modifié par la fonction passée en argument. Si l’on n’avait pas mappé (+100) sur pop, elle aurait retourné (1,[2,3,4]).

Voici comment liftM est implémentée :

liftM :: (Monad m) => (a -> b) -> m a -> m b liftM f m = m >>= (\x -> return (f x)) Ou, en notation do : liftM :: (Monad m) => (a -> b) -> m a -> m b liftM f m = do x <- m return (f x)

On donne la valeur monadique m à la fonction, et on applique f à son résultat avant de le retourner dans un contexte par défaut. Grâce aux lois des monades, on a la garantie que le contexte est inchangé, seule la valeur résultante est altérée. On voit que liftM est implémentée sans faire référence à la classe de types

Functor . Cela signifie qu’on a pu implémenter fmap (ou liftM , peu importe son nom) en utilisant simplement ce que les monades nous offraient. Ainsi, on peut conclure que les monades sont plus fortes que les foncteurs normaux.

La classe de types Applicative nous permet d’appliquer des fonctions entre des valeurs dans des contextes comme si elles étaient des valeurs normales. Comme cela :

ghci> (+) <$> Just 3 <*> Just 5 Just 8

ghci> (+) <$> Just 3 <*> Nothing Nothing

Utiliser ce style applicatif rend les choses plutôt faciles. <$> est juste fmap et <*> est une fonction de la classe de types Applicative qui a pour type : (<*>) :: (Applicative f) => f (a -> b) -> f a -> f b

C’est donc un peu comme fmap, seulement la fonction elle-même est dans un contexte. Il faut d’une certaine façon l’extraire de ce contexte et la mapper sur la valeur f a, puis restaurer le contexte. Grâce à la curryfication par défaut en Haskell, on peut utiliser la combinaison de <$> et <*> pour appliquer des fonctions qui prennent plusieurs paramètres entre plusieurs valeurs applicatives.

Il s‘avère que tout comme fmap, <*> peut aussi être implémentée uniquement avec ce que la classe de types Monad nous donne. La fonction ap est simplement <*> , mais avec une contrainte de classe Monad plutôt qu’ Applicative . Voici sa définition :

ap :: (Monad m) => m (a -> b) -> m a -> m b

ap mf m = do

f <- mf x <- m

return (f x)

mf est une valeur monadique dont le résultat est une fonction. Puisque la fonction est dans un contexte comme la valeur, on récupère la fonction de son contexte et on l’appelle f, puis on récupère la valeur qu’on appelle x et finalement on applique la fonction avec la valeur et on présente le résultat. Voici un exemple rapide :

ghci> Just (+3) <*> Just 4 Just 7

ghci> Just (+3) `ap` Just 4 Just 7

ghci> [(+1),(+2),(+3)] <*> [10,11] [11,12,12,13,13,14]

ghci> [(+1),(+2),(+3)] `ap` [10,11] [11,12,12,13,13,14]

On voit à présent que les monades sont plus fortes que les foncteurs applicatifs, puisqu‘on peut utiliser les fonctions de Monad pour implémenter celles d ’ Applicative. En fait, très souvent, lorsqu‘un type s’avère être une monade, les gens écrivent d’abord l’instance de Monad, puis créent une instance d ’ Applicative en disant que pure est return et <*> est ap. De façon similaire, si vous avec une instance de Monad, vous pouvez faire une instance de

Functor en disant que fmap est liftM .

La fonction liftA2 est pratique pour appliquer une fonction entre deux valeurs applicatives. Elle est simplement définie comme :

liftA2 :: (Applicative f) => (a -> b -> c) -> f a -> f b -> f c

liftA2 f x y = f <$> x <*> y

La fonction liftM2 fait la même chose, mais avec une contrainte Monad. Il existe également liftM3, liftM4 et liftM5.

Nous avons vu que les monades étaient plus puissantes que les foncteurs applicatifs et les foncteurs et que bien que toutes les monades soient des foncteurs applicatifs et des foncteurs, elles n’ont pas nécessairement d’instances de Functor et Applicative, et on a donc vu les fonctions équivalentes à celles qu’on utilisait sur nos foncteurs et nos foncteurs applicatifs.