• Aucun résultat trouvé

Pour vous faire une idée de l’ordre de grandeur de l’amélioration des performances en utilisant les listes différentielles, considérez cette fonction qui décompte à partir d’un nombre jusqu’à zéro, et produit son registre dans le sens inverse, comme gcdReverse, de manière à ce que le registre compte les nombres dans l’ordre croissant :

finalCountDown :: Int -> Writer (DiffList String) ()

finalCountDown 0 = do

tell (toDiffList ["0"])

finalCountDown x = do

finalCountDown (x-1)

tell (toDiffList [show x])

Si on lui donne 0, elle l’écrit simplement dans le registre. Pour tout autre nombre, elle commence d’abord par décompter depuis son prédécesseur, jusqu’à 0 et enfin, ajoute ce nombre au registre. Ainsi, si l’on applique finalCountDown à 100, la chaîne de caractères "100" sera la dernière du registre.

Si vous chargez cete fonction dans GHCi et que vous l’appliquez à un nombre gros, comme 500000, vous verrez qu’elle compte rapidement depuis 0 vers l’avant :

ghci> mapM_ putStrLn . fromDiffList . snd . runWriter $ finalCountDown 500000 0

1 2 ...

Cependant, si on la change pour utiliser des listes normales plutôt que des listes différentielles, de cette manière :

finalCountDown :: Int -> Writer [String] ()

finalCountDown 0 = do

tell ["0"]

finalCountDown x = do

finalCountDown (x-1) tell [show x]

Et qu’on demande à GHCi de compter :

ghci> mapM_ putStrLn . snd . runWriter $ finalCountDown 500000

On voit que le décompte est très lent.

Bien sûr, ce n’est pas la façon rigoureuse et scientifique de tester la vitesse de nos programmes, mais on peut déjà voir que dans ce cas, utiliser des listes différentielles commence à fournir des résultats immédiatement alors que pour des listes normales, cela prend une éternité.

Oh, au fait, la chanson Final Countdown d’Europe est maintenant coincée dans votre tête. De rien !

La lire ? Pas cette blague encore.

Dans le chapitre sur les foncteurs applicatifs, on a vu que le type des fonctions, (->) r, était une instance de Functor . Mapper une fonction f sur une fonction g crée une fonction qui prend la même chose que g , applique g dessus puis applique f au résultat. En gros, on crée une nouvelle fonction qui est comme g , mais qui applique f avant de renvoyer son résultat. Par exemple :

ghci> let f = (*5)

ghci> let g = (+3)

ghci> (fmap f g) 8 55

Nous avons aussi vu que les fonctions étaient des foncteurs applicatifs. Elles nous permettaient d’opérer sur les résultats à terme de fonctions comme si l’on avait déjà ces résultats. Par exemple :

ghci> let f = (+) <$> (*2) <*> (+10)

ghci> f 3 19

L’expression (+) <$> (*2) <*> (+10) crée une fonction qui prend un nombre, donne ce nombre à (*2) et à (+10), et somme les résultats. Par exemple, si l’on applique cette fonction à 3, elle applique à la fois (*2) et (+10) sur 3, résultant en 6 et 13. Puis, elle appelle (+) avec 6 et 13 et le résultat est 19. Non seulement le type des fonctions (->) r est un foncteur et un foncteur applicatif, mais c’est aussi une monade. Tout comme les autres valeurs monadiques que l’on a croisées jusqu’ici, une fonction peut être considérée comme une valeur dans un contexte. Le contexte dans le cas des fonctions est que la valeur n’est pas encore là, et qu’il faudra donc appliquer à la fonction sur quelque chose pour obtenir la valeur résultante.

Puisqu’on a déjà vu comment les fonctions fonctionnent comme des foncteurs et des foncteurs applicatifs, plongeons immédiatement dans le grand bassin et voyons l’instance de Monad. Elle est située dans Control.Monad.Instances et ressemble à ça :

instance Monad ((->) r) where

return x = \_ -> x

h >>= f = \w -> f (h w) w

Nous avons déjà vu comment pure était implémentée pour les fonctions, et return est la même chose que pure. Elle prend une valeur, et la place dans un contexte minimal contenant cette valeur comme résultat. Et le seul moyen de créer une fonction qui renvoie toujours le même résultat consiste à lui faire ignorer complètement son paramètre.

L’implémentation de >>= semble un peu plus cryptique, mais elle ne l’est pas tant que ça. Quand on utilisait >>= pour donner des valeurs monadiques à une fonction, le résultat était toujours une valeur monadique. Dans ce cas, si l’on donne une fonction à une autre fonction, le résultat est toujours une fonction. C’est pourquoi le résultat commence comme une lambda. Toutes les implémentations de >>= qu’on a vues jusqu’ici isolaient d’une certaine manière le résultat de la valeur monadique et lui appliquaient la fonction f. C’est la même chose ici. Pour obtenir le résultat d’une fonction, il faut l’appliquer à quelque chose, ce qu’on fait avec (h w), puis on applique f au résultat. f retourne une valeur monadique, qui est une fonction dans notre cas, donc on l’applique également à w.

Si vous ne comprenez pas comment >>= marche ici, ne vous inquiétez pas, avec les exemples on va voir que c’est simplement une monade comme une autre. Voici une expression do qui utilise cette monade :

import Control.Monad.Instances

addStuff :: Int -> Int

addStuff = do

a <- (*2) b <- (+10) return (a+b)

C’est la même chose que l’expression applicative qu’on a écrite plus haut, seulement maintenant elle se base sur le fait que les fonctions soient des monades. Une expression do résulte toujours en une valeur monadique, et celle-ci ne fait pas exception. Le résultat de cette valeur monadique est une fonction. Ce qui se passe ici, c’est qu’elle prend un nombre, puis fait (*2) sur ce nombre, ce qui résulte en a. (+10) est également appliquée au même nombre que celui donné à

(*2) , et le résultat devient b . return , comme dans les autres monades, n’a pas d’autre effet que de créer une valeur monadique présentée en résultat. Elle présente ici a + b comme le résultat de cette fonction. Si on essaie, on obtient le même résultat qu’avant :

ghci> addStuff 3 19

(*2) et (+3) sont appliquées au nombre 3 dans ce cas. return (a+b) est également appliquée au nombre 3 , mais elle ignore ce paramètre et retourne toujours a+b en résultat. Pour cette raison, la monade des fonctions est aussi appelée la monade de lecture. Toutes les fonctions lisent en effet la même source. Pour illustrer cela encore mieux, on peut réécrire addStuff ainsi :

addStuff :: Int -> Int

addStuff x = let

a = (*2) x b = (+10) x in a+b

On voit que la monade de lecture nous permet de traiter les fonctions comme des valeurs dans un contexte. On peut agir comme si l’on savait déjà ce que les fonctions retournaient. Ceci est réussi en collant toutes les fonctions ensemble et en donnant le paramètre de la fonction ainsi créée à toutes les fonctions qui la

composent. Ainsi, si l’on a plein de fonctions qui attendent toutes un même paramètre, on peut utiliser la monade de lecture pour extraire en quelque sorte leur résultat, et l’implémentation de >>= s’assurera que tout se passe comme prévu.