From ba5b428d27e577fcff811fa34fa791440a6b03da Mon Sep 17 00:00:00 2001 From: Valentin Robert Date: Sun, 11 Sep 2011 14:32:00 +0200 Subject: [PATCH] Lots of fixes --- chapitres.mkd | 2 +- ...-nos-propres-types-et-classes-de-types.mkd | 116 ++--- demarrons.mkd | 90 ++-- entrees-et-sorties.mkd | 403 +++++++++--------- et-pour-quelques-monades-de-plus.mkd | 340 ++++++++------- ...eurs-foncteurs-applicatifs-et-monoides.mkd | 320 +++++++------- fonctions-d-ordre-superieur.mkd | 198 +++++---- html/hscolour.css | 6 +- introduction.mkd | 21 +- modules.mkd | 120 +++--- pour-une-poignee-de-monades.mkd | 242 ++++++----- recursivite.mkd | 66 +-- resoudre-des-problemes-fonctionnellement.mkd | 105 ++--- syntaxe-des-fonctions.mkd | 87 ++-- types-et-classes-de-types.mkd | 41 +- zippeurs.mkd | 110 ++--- 16 files changed, 1228 insertions(+), 1039 deletions(-) diff --git a/chapitres.mkd b/chapitres.mkd index b603bc0..31d1e0e 100644 --- a/chapitres.mkd +++ b/chapitres.mkd @@ -99,7 +99,7 @@ Apprenez Haskell pour le meilleur ! #. [Zippeurs](zippeurs) + [Une petite balade](zippeurs#une-petite-balade) + [Une traînée de miettes](zippeurs#une-trainee-de-miettes) - + [Concentrons-nous sur les listes](zippeurs#concentrons-nous-sur-les-listes) + + [Se focaliser sur des listes](zippeurs#se-focaliser-sur-des-listes) + [Un système de fichiers élémentaire](zippeurs#un-systeme-de-fichiers-elementaire) + [Attention à la marche](zippeurs#attention-a-la-marche) diff --git a/creer-nos-propres-types-et-classes-de-types.mkd b/creer-nos-propres-types-et-classes-de-types.mkd index 4fa1c59..156f307 100644 --- a/creer-nos-propres-types-et-classes-de-types.mkd +++ b/creer-nos-propres-types-et-classes-de-types.mkd @@ -3,13 +3,17 @@
@@ -32,11 +36,11 @@ type `Bool` est défini dans la bibliothèque standard. `data` signifie qu'on crée un nouveau type de données. La partie avant le `=` dénote le type, ici `Bool`. Les choses après le `=` sont des **constructeurs de valeurs**. Ils spécifient les différentes valeurs que peut prendre ce type. Le -`|` se lit comme un *ou*. On peut donc lire cette déclaration : le type `Bool` -peut avoir pour valeur `True` ou `False`. Le nom du type tout comme les noms -des constructeurs de valeurs doivent commencer par une majuscule. +`|` se lit comme un *ou*. On peut donc lire cette déclaration : le type +`Bool` peut avoir pour valeur `True` ou `False`. Le nom du type tout comme les +noms des constructeurs de valeurs doivent commencer par une majuscule. -De manière similaire, on peut imaginer `Int` défini ainsi : +De manière similaire, on peut imaginer `Int` défini ainsi : > data Int = -2147483648 | -2147483647 | ... | -1 | 0 | 1 | 2 | ... | 2147483647 @@ -53,7 +57,7 @@ possibilité serait d'utiliser des tuples. Un cercle pourrait être `(43.1, 55.0 la troisième le rayon. Ça a l'air raisonnable, mais ça pourrait tout aussi bien représenter un vecteur 3D ou je ne sais quoi. Une meilleure solution consiste à créer notre type pour représenter les données. Disons qu'une forme puisse être -un cercle ou un rectangle. Voilà : +un cercle ou un rectangle. Voilà : > data Shape = Circle Float Float Float | Rectangle Float Float Float Float @@ -105,14 +109,14 @@ afficher ce type de données sous une forme littérale. Rappelez-vous, quand on essaie d'afficher une valeur dans l'invite, Haskell exécute la fonction `show` sur cette valeur pour obtenir une représentation en chaîne de caractères, puis affiche celle-ci dans le terminal. Pour rendre `Shape` membre de la classe de -types `Show`, on peut le modifier ainsi : +types `Show`, on peut le modifier ainsi : > data Shape = Circle Float Float Float | Rectangle Float Float Float Float deriving (Show) On ne s'intéressera pas trop à la dérivation pour l'instant. Disons seulement qu'en ajoutant `deriving (Show)` à la fin d'une déclaration *data*, Haskell rend magiquement ce type membre de la classe de types `Show`. Ainsi, on peut à -présent faire : +présent faire : > ghci> Circle 10 20 5 > Circle 10.0 20.0 5.0 @@ -121,7 +125,7 @@ présent faire : Les constructeurs de valeurs sont des fonctions, on peut donc les mapper, les appliquer partiellement, etc. Si l'on veut créer une liste de cercles -concentriques de différents rayons, on peut faire : +concentriques de différents rayons, on peut faire : > ghci> map (Circle 10 20) [4,5,6,6] > [Circle 10.0 20.0 4.0,Circle 10.0 20.0 5.0,Circle 10.0 20.0 6.0,Circle 10.0 20.0 6.0] @@ -189,7 +193,7 @@ constructeurs de valeurs que vous voulez exporter, séparés par des virgules. S vous voulez exporter tous les constructeurs, vous pouvez écrire `..`. Si on voulait exporter les fonctions et types définis ici dans un module, on -pourrait commencer ainsi : +pourrait commencer ainsi : > module Shapes > ( Point(..) @@ -229,7 +233,7 @@ Syntaxe des enregistrements enregistrement OK, on vient de nous donner la tâche de créer un type de données pour décrire -une personne. Les informations à stocker pour décrire une personne sont : +une personne. Les informations à stocker pour décrire une personne sont : prénom, nom de famille, âge, poids, numéro de téléphone, et parfum de glace préféré. Je ne sais pas pour vous, mais c'est tout ce que je souhaite savoir à propos d'une personne. Allons-y ! @@ -245,7 +249,7 @@ personne. Plutôt cool, bien qu'un peu illisible. Comment faire une fonction pour récupérer juste une information sur la personne ? Une pour son prénom, une pour -son nom, etc. Nous devrions les définir ainsi : +son nom, etc. Nous devrions les définir ainsi : > firstName :: Person -> String > firstName (Person firstname _ _ _ _ _) = firstname @@ -298,7 +302,7 @@ autres et de les séparer par des espaces, on peut utiliser des accolades. On appelé Paamayim Nekudotayim, haha) puis on spécifie son type. Le type de données résultant est exactement le même. Le principal avantage est que cela crée les fonctions pour examiner chaque champ du type de données. En utilisant -la syntaxe des enregistrements, Haskell a automatiquement créé ces fonctions : +la syntaxe des enregistrements, Haskell a automatiquement créé ces fonctions : `firstName`, `lastName`, `age`, `height`, `phoneNumber` et `flavor`. > ghci> :t flavor @@ -409,21 +413,21 @@ utiliser a un sens. Généralement, on les utilise quand notre type de données fonctionne sans se soucier du type de ce qu'il contient en lui, comme pour notre type `Maybe a`. Si notre type se comporte comme une sorte de boîte, il est bien de les utiliser. On pourrait changer notre type de données `Car` de -ceci : +ceci : > data Car = Car { company :: String > , model :: String > , year :: Int > } deriving (Show) -en cela : +en cela : > data Car a b c = Car { company :: a > , model :: b > , year :: c > } deriving (Show) -Mais y gagnerait-on vraiment ? La réponse est : probablement pas, parce qu'on +Mais y gagnerait-on vraiment ? La réponse est : probablement pas, parce qu'on finirait par définir des fonctions qui ne fonctionnent que sur le type `Car String String Int`. Par exemple, vu notre première définition de `Car`, on pourrait écrire une fonction qui affiche les propriétés de la voiture avec un @@ -475,7 +479,7 @@ un bon exemple d'endroit où les types paramétrés sont très utiles. Avoir des maps paramétrées nous permet de créer des maps de n'importe quel type vers n'importe quel autre type, du moment que le type de la clé soit membre de la classe de types `Ord`. Si nous étions en train de définir un type de map, on -pourrait ajouter la contrainte de classe dans la déclaration *data* : +pourrait ajouter la contrainte de classe dans la déclaration *data* : > data (Ord k) => Map k v = ... @@ -495,7 +499,7 @@ dans sa déclaration *data*, le type de `ToList` devrait être `toList :: (Ord k => Map k a -> [(k, a)]`, alors que cette fonction ne fait aucune comparaison de clés selon leur ordre. -Conclusion : ne mettez pas de contraintes de classe dans les déclarations +Conclusion : ne mettez pas de contraintes de classe dans les déclarations *data* même lorsqu'elles ont l'air sensées, parce que de toute manière, vous devrez les écrire dans les fonctions qui en dépendent. @@ -555,7 +559,7 @@ Dans la section [Classes de types 101](types-et-classes-de-types#classes-de-types-101), nous avons vu les bases des classes de types. Nous avons dit qu'une classe de types est une sorte d'interface qui définit un comportement. Un type peut devenir une **instance** -d'une classe de types s'il supporte ce comportement. Par exemple : le type +d'une classe de types s'il supporte ce comportement. Par exemple : le type `Int` est une instance d'`Eq` parce que cette classe définit le comportement de ce qui peut être testé pour l'égalité. Et puisqu'on peut tester l'égalité de deux entiers, `Int` est membre de la classe `Eq`. La vraie utilité vient des @@ -577,12 +581,12 @@ S'il peut être ordonné, on le rend instance de la classe `Ord`. Dans la prochaine section, nous verrons comment créer manuellement nos instances d'une classe de types en implémentant les fonctions définies par cette classe. Pour l'instant, voyons comment Haskell peut magiquement faire de -nos types des instances de n'importe laquelle des classes de types suivantes : +nos types des instances de n'importe laquelle des classes de types suivantes : `Eq`, `Ord`, `Enum`, `Bounded`, `Show` et `Read`. Haskell peut dériver le comportement de nos types dans ces contextes si l'on utilise le mot clé *deriving* lors de la création du type de données. -Considérez ce type de données : +Considérez ce type de données : > data Person = Person { firstName :: String > , lastName :: String @@ -679,7 +683,7 @@ type ayant été construites par deux constructeurs différents, la valeur construite par le constructeur défini en premier sera considérée plus petite. Par exemple, considérez le type `Bool`, qui peut avoir pour valeur `False` ou `True`. Pour comprendre comment il fonctionne lorsqu'il est comparé, on peut -l'imaginer défini comme : +l'imaginer défini comme : > data Bool = False | True deriving (Ord) @@ -713,7 +717,7 @@ sont des fonctions, qui ne sont pas des instances d'`Ord`. On peut facilement utiliser des types de données algébriques pour créer des énumérations, et les classes de types `Enum` et `Bounded` nous aident dans la -tâche. Considérez les types de données suivants : +tâche. Considérez les types de données suivants : > data Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday @@ -851,7 +855,7 @@ fonctions. Les synonymes de types peuvent aussi être paramétrés. Si on veut un type qui représente le type des listes associatives de façon assez générale pour être -utilisé quelque que soit le type des clés et des valeurs, on peut faire : +utilisé quelque que soit le type des clés et des valeurs, on peut faire : > type AssocList k v = [(k,v)] @@ -862,7 +866,7 @@ type concret, comme `AssocList Int String` par exemple.
-**Fonzie dit** : Hey ! Quand je parle de *types concrets*, je veux dire genre +**Fonzie dit** : Hey ! Quand je parle de *types concrets*, je veux dire genre des types appliqués complètement comme `Map Int String`, ou si on se frotte à une de ces fonctions polymorphes, `[a]` ou `(Ord a) => Maybe a` et tout ça. Et tu vois, parfois moi et mes potes on dit que `Maybe` c'est un type, mais bon, @@ -880,11 +884,11 @@ pour obtenir de nouveaux constructeurs de types. Tout comme on appelle une fonctions avec trop peu de paramètres, on peut spécifier trop peu de paramètres de types à un constructeur de types et obtenir un constructeur de types partiellement appliqué. Si l'on voulait un type qui représente une map (de -`Data.Map`) qui va des entiers vers quelque chose, on pourrait faire soit : +`Data.Map`) qui va des entiers vers quelque chose, on pourrait faire soit : > type IntMap v = Map Int v -Ou bien : +Ou bien : > type IntMap = Map Int @@ -915,7 +919,7 @@ situe après un `::`. Le `::` peut être dans des déclarations de types ou pour des annotations de types. Un autre type de données cool qui prend deux types en paramètres est `Either a -b`. Voici grossièrement sa définition : +b`. Voici grossièrement sa définition : > data Either a b = Left a | Right b deriving (Eq, Ord, Read, Show) @@ -949,7 +953,7 @@ fonction a échoué ou pourquoi, on utilise généralement le type `Either a b`, erreurs utilisent le constructeur de valeurs `Left`, alors que les résultats utilisent le constructeur de valeurs `Right`. -Exemple : un lycée a des casiers dans lesquels les étudiants peuvent ranger +Exemple : un lycée a des casiers dans lesquels les étudiants peuvent ranger leurs posters de Guns'n'Roses. Chaque casier a une combinaison. Quand un étudiant veut un nouveau casier, il indique au superviseur des casiers le numéro de casier qu'il voudrait, et celui-ci lui donne le code. Cependant, si @@ -992,7 +996,7 @@ s'il est utilisé. Si c'est le cas, on retourne `Left` en indiquant qu'il est déjà pris. Autrement, on retourne une valeur de type `Right Code`, dans laquelle on donne le code dudit casier. En fait, c'est un `Right String`, mais on a introduit un synonyme de types pour introduire un peu de documentation -dans la déclaration de types. Voici une map à titre d'exemple : +dans la déclaration de types. Voici une map à titre d'exemple : > lockers :: LockerMap > lockers = Map.fromList @@ -1034,7 +1038,7 @@ sont de ce même type ! Ainsi, on peut créer des types de données récursifs, une valeur d'un type peut contenir des valeurs de ce même type, qui à leur tour peuvent contenir encore plus de valeurs de ce type, et ainsi de suite. -Pensez à cette liste : `[5]`. C'est juste un sucre syntaxique pour `5:[]`. À +Pensez à cette liste : `[5]`. C'est juste un sucre syntaxique pour `5:[]`. À gauche de `:`, il y a une valeur, et à droite, il y a une liste. Dans ce cas, c'est une liste vide. Maintenant, qu'en est-il de `[4, 5]` ? Eh bien, ça se désucre en `4:(5:[])`. En considérant le premier `:`, on voit qu'il prend aussi @@ -1095,7 +1099,7 @@ plus grande fixité, et ainsi `5 * 4 + 3` est équivalent à `(5 * 4) + 3`. À part ce détail, on a juste écrit `a :-: (List a)` à la place de `Cons a (List a)`. Maintenant, on peut écrire des listes qui ont notre type de listes de la -sorte : +sorte : > ghci> 3 :-: 4 :-: 5 :-: Empty > (:-:) 3 ((:-:) 4 ((:-:) 5 Empty)) @@ -1108,7 +1112,7 @@ constructeur comme une fonction préfixe, d'où le parenthésage autour de l'opérateur (rappelez-vous, `4 + 3` est juste `(+) 4 3`). Créons une fonction qui somme deux de nos listes ensemble. `++` est défini -ainsi pour des listes normales : +ainsi pour des listes normales : > infixr 5 ++ > (++) :: [a] -> [a] -> [a] @@ -1146,7 +1150,7 @@ caractères respectivement. arbre binaire À présent, implémentons un **arbre binaire de recherche**. Si vous ne -connaissez pas les arbres binaires de recherche, voici en quoi ils consistent : +connaissez pas les arbres binaires de recherche, voici en quoi ils consistent : chaque élément pointe vers deux éléments, son fils gauche et son fils droit. Le fils gauche a une valeur inférieure à celle de l'élément, le fils droit a une valeur supérieure. Chacun des fils peut à son tour pointer vers zéro, un ou @@ -1164,7 +1168,7 @@ si 8 est là ou pas. Les ensembles de `Data.Set` et les maps de `Data.Map` sont implémentés à l'aide d'arbres binaires de recherche équilibrés, qui sont toujours bien équilibrés. -Voilà ce qu'on va dire : un arbre est soit vide, soit un élément contenant une +Voilà ce qu'on va dire : un arbre est soit vide, soit un élément contenant une valeur et deux arbres. Ça sent le type de données algébrique à plein nez ! > data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving (Show, Read, Eq) @@ -1281,7 +1285,7 @@ instances de ces classes en demandant à Haskell de les dériver pour nous. Dans cette section, on va créer nos propres classes de types, et nos propres instances, le tout à la main. -Bref récapitulatif sur les classes de types : elles sont comme des interfaces. +Bref récapitulatif sur les classes de types : elles sont comme des interfaces. Une classe de types définit un comportement (tester l'égalité, comparer l'ordre, énumérer), puis les types qui peuvent se comporter de la sorte sont fait instances de ces classes. Le comportement des classes de types est défini @@ -1300,7 +1304,7 @@ Par exemple, la classe `Eq` est pour les choses qui peuvent être testées `Car`) et que comparer deux voitures avec `==` a un sens, alors il est sensé de rendre `Car` instance d'`Eq`. -Voici comment `Eq` est définie dans le prélude : +Voici comment `Eq` est définie dans le prélude : > class Eq a where > (==) :: a -> a -> Bool @@ -1341,7 +1345,7 @@ type de cette fonction, celui-ci sera `(Eq a) => a -> a -> Bool`. Maintenant qu'on a une classe, que faire avec ? Eh bien, pas grand chose, vraiment. Mais une fois qu'on a des instances, on commence à bénéficier de -fonctionnalités sympathiques. Regardez donc ce type : +fonctionnalités sympathiques. Regardez donc ce type : > data TrafficLight = Red | Yellow | Green @@ -1370,7 +1374,7 @@ automatiquement. On dit que c'est une définition complète minimale d'une class de types - un des plus petits ensembles de fonctions à implémenter pour que le type puisse se comporter comme une instance de la classe. Pour remplir la définition complète minimale d'`Eq`, il faut redéfinir soit `==`, soit `/=`. -Cependant, si `Eq` était définie seulement ainsi : +Cependant, si `Eq` était définie seulement ainsi : > class Eq a where > (==) :: a -> a -> Bool @@ -1378,7 +1382,7 @@ Cependant, si `Eq` était définie seulement ainsi : alors nous aurions dû implémenter ces deux fonctions lors de la création d'une instance, car Haskell ne saurait pas comment elles sont reliées. La définition -complète minimale serait alors : `==` et `/=`. +complète minimale serait alors : `==` et `/=`. Vous pouvez voir qu'on a implémenté `==` par un simple filtrage par motif. Puisqu'il y a beaucoup de cas où deux lumières sont différentes, on a spécifié @@ -1395,7 +1399,7 @@ prend une valeur et retourne une chaîne de caractères. > show Green = "Green light" Encore une fois, nous avons utilisé le filtrage par motif pour arriver à nos -fins. Voyons cela en action : +fins. Voyons cela en action : > ghci> Red == Red > True @@ -1413,7 +1417,7 @@ light"`, il faut faire la déclaration d'instance à la main. Vous pouvez également créer des classes de types qui sont des sous-classes d'autres classes de types. La déclaration de classe de `Num` est un peu longue, -mais elle débute ainsi : +mais elle débute ainsi : > class (Eq a) => Num a where > ... @@ -1435,7 +1439,7 @@ de classes de types ? Ce qui rend `Maybe` différent de, par exemple, `TrafficLight`, c'est que `Maybe` tout seul n'est pas un type concret, mais un constructeur de types qui prend un type en paramètre (comme `Char` ou un autre) pour produire un type concret (comme `Maybe Char`). Regardons à nouveau la -classe de types `Eq` : +classe de types `Eq` : > class Eq a where > (==) :: a -> a -> Bool @@ -1447,7 +1451,7 @@ De ces déclarations de types, on voit que `a` est utilisé comme un type concre car tous les types entre les flèches d'une fonction doivent être concrets (souvenez-vous, on ne peut avoir de fonction de type `a -> Maybe`, mais on peut avoir des fonctions `a -> Maybe a` ou `Maybe Int -> Maybe String`). C'est -pour cela qu'on ne peut pas faire : +pour cela qu'on ne peut pas faire : > instance Eq Maybe where > ... @@ -1456,7 +1460,7 @@ Parce que, comme on l'a vu, `a` doit être un type concret, et `Maybe` ne l'est pas. C'est un constructeur de types qui prend un paramètre pour produire un type concret. Il serait également très fastidieux d'écrire des instances `instance Eq (Maybe Int) where`, `instance Eq (Maybe Char) where`, etc. pour -chaque type qu'on utilise. Ainsi, on peut écrire : +chaque type qu'on utilise. Ainsi, on peut écrire : > instance Eq (Maybe m) where > Just x == Just y = x == y @@ -1475,7 +1479,7 @@ d'en faire une instance d'`Eq`. Il y a un problème à cela tout de même. Le voyez-vous ? On utilise `==` sur le contenu de `Maybe`, mais on n'a pas de garantie que ce que contient `Maybe` est membre d'`Eq` ! C'est pourquoi nous devons modifier la déclaration d'*instance* -ainsi : +ainsi : > instance (Eq m) => Eq (Maybe m) where > Just x == Just y = x == y @@ -1483,7 +1487,7 @@ ainsi : > _ == _ = False On a dû ajouter une contrainte de classe ! Avec cette déclaration d'*instance*, -on dit ceci : nous voulons que tous les types de la forme `Maybe m` soient +on dit ceci : nous voulons que tous les types de la forme `Maybe m` soient membres de la classe `Eq`, à condition que le type `m` (celui dans le `Maybe`) soit lui-même membre d'`Eq`. C'est ce qu'Haskell dériverait d'ailleurs. @@ -1530,7 +1534,7 @@ Une classe de types oui-non En JavaScript et dans d'autres langages à typage faible, on peut mettre quasiment ce que l'on veut dans une expression *if*. Par exemple, tout ceci est -valide : `if (0) alert ("YEAH") else alert("NO!")`, `if ("") alert ("YEAH") +valide : `if (0) alert ("YEAH") else alert("NO!")`, `if ("") alert ("YEAH") else alert("NO!")`, `if (false) alert ("YEAH") else alert("NO!")`, etc. et tout cela lancera une alerte `NO!`. Si vous faites `if ("WHAT") alert ("YEAH") else alert("NO!")`, une alerte `"YEAH!"` sera lancée parce que JavaScript considère @@ -1678,7 +1682,7 @@ type dans la classe de type était un type concret, comme le `a` dans `(==) :: (Eq a) => a -> a -> Bool`. Mais maintenant, le `f` n'est pas un type concret (un type qui peut contenir des valeurs, comme `Int`, `Bool` ou `Maybe String`), mais un constructeur de types qui prend un paramètre de type. Petit -rafraîchissement par l'exemple : `Maybe Int` est un type concret, mais `Maybe` +rafraîchissement par l'exemple : `Maybe Int` est un type concret, mais `Maybe` est un constructeur qui prend un type en paramètre. Bon, on voit que `fmap` prend une fonction d'un type vers un autre type et un foncteur appliqué au premier type, et retourne un foncteur appliqué au second type. @@ -1777,7 +1781,7 @@ La classe de types `Functor` veut des constructeurs de types à un paramètre de type, mais `Either` en prend deux. Hmmm ! Je sais, on va partiellement appliquer `Either` en lui donnant un paramètre de type, de façon à ce qu'il n'ait plus qu'un paramètre libre. Voici comment `Either a` est fait foncteur -dans la bibliothèque standard : +dans la bibliothèque standard : > instance Functor (Either a) where > fmap f (Right x) = Right (f x) @@ -1790,7 +1794,7 @@ types qui attend un paramètre, alors qu'`Either` en attend deux. Si `fmap` Either a b -> Either a c`, parce que c'est identique à `(b -> c) -> (Either a) b -> (Either a) c`. Dans l'implémentation, on a mappé sur le constructeur de valeurs `Right`, mais pas sur `Left`. Pourquoi cela ? Eh bien, si on retourne -à la définition d'`Either a b`, c'est un peu comme : +à la définition d'`Either a b`, c'est un peu comme : > data Either a b = Left a | Right b @@ -1932,7 +1936,7 @@ l'appliquer partiellement parce que `Functor` voulait des types qui attendent un paramètre, alors qu'`Either` en prend deux. En d'autres mots, `Functor` veut des types de sorte `* -> *` et nous avons dû appliquer partiellement `Either` pour obtenir un type de sorte `* -> *` au lieu de sa sorte originale `* -> * -> -*`. Si on regarde à nouveau la définition de `Functor` : +*`. Si on regarde à nouveau la définition de `Functor` : > class Functor f where > fmap :: (a -> b) -> f a -> f b @@ -1944,7 +1948,7 @@ que les types qui peuvent être amis avec `Functor` doivent avoir pour sorte `* -> *`. Maintenant, faisons un peu de type-fu. Regardez cette classe de types que je -vais inventer maintenant : +vais inventer maintenant : > class Tofu t where > tofu :: j a -> t a j @@ -2003,7 +2007,7 @@ remplaçait `j` par `Frank`, le type résultant serait `Frank Int Maybe`. > Frank {frankField = ["HELLO"]} Pas très utile, mais bon pour l'entraînement de nos muscles de types. Encore un -peu de type-fu. Mettons qu'on ait ce type de données : +peu de type-fu. Mettons qu'on ait ce type de données : > data Barry t k p = Barry { yabba :: p, dabba :: t k } @@ -2022,7 +2026,7 @@ Vérifions avec GHCi. Ah, on avait raison. Comme c'est satisfaisant. Maintenant, pour rendre ce type membre de `Functor` il nous faut appliquer partiellement les deux premiers paramètres de type de façon à ce qu'il nous reste un `* -> *`. Cela veut dire -que le début de la déclaration d'instance sera : `instance Functor (Barry a b) +que le début de la déclaration d'instance sera : `instance Functor (Barry a b) where`. Si on regarde `fmap` comme si elle était faite spécifiquement pour `Barry`, elle aurait pour type `fmap :: (a -> b) -> Barry c d a -> Barry c d b`, parce qu'on remplace juste les `f` de `Functor` par `Barry c d`. Le @@ -2053,13 +2057,17 @@ de types d'Haskell.
diff --git a/demarrons.mkd b/demarrons.mkd index 9297c68..90fa902 100644 --- a/demarrons.mkd +++ b/demarrons.mkd @@ -3,13 +3,18 @@
@@ -83,7 +88,7 @@ de `True` et `False` (NdT: respectivement vrai et faux). > ghci> not (True && True) > False -Un test d'égalité s'écrit ainsi : +Un test d'égalité s'écrit ainsi : > ghci> 5 == 5 > True @@ -114,8 +119,8 @@ pas. Là où `+` ne fonctionne que sur ce qui est considéré comme un nombre, ` marche sur n'importe quelles deux choses qui peuvent être comparées. Le piège, c'est qu'elles doivent être toutes les deux du même type de choses. On ne compare pas des pommes et des oranges. Nous nous intéresserons de plus près aux -types plus tard. Notez : vous pouvez tout de même faire `5 + 4.0` car `5` est -vicieux et peut se faire passer pour un entier ou pour un nombre à virgule +types plus tard. Notez : vous pouvez tout de même faire `5 + 4.0` car `5` +est vicieux et peut se faire passer pour un entier ou pour un nombre à virgule flottante. `4.0` ne peut pas se faire passer pour un entier, donc c'est à `5` de s'adapter à lui. @@ -144,7 +149,7 @@ successeur. Comme vous pouvez le voir, on sépare le nom de la fonction du paramètre par un espace. Appeler une fonction avec plusieurs paramètres est aussi simple. Les fonctions `min` et `max` prennent deux choses qu'on peut ordonner (comme des nombres !). `min` retourne la plus petite, `max` la plus -grande. Voyez vous-mêmes : +grande. Voyez vous-mêmes : > ghci> min 9 10 > 9 @@ -234,7 +239,7 @@ baby` dans GHCi). Comme attendu, vous pouvez appeler vos propres fonctions depuis les autres fonctions que vous aviez créées. Avec cela en tête, redéfinissons `doubleUs` -ainsi : +ainsi : > doubleUs x y = doubleMe x + doubleMe y @@ -283,7 +288,7 @@ caractère valide à utiliser dans un nom de fonction. On utilise généralement `'` pour indiquer la version stricte d'une fonction (une version qui n'est pas paresseuse) ou pour la version légèrement modifiée d'une fonction ou d'une variable. Puisque `'` est un caractère valide dans le nom d'une fonction, on -peut écrire : +peut écrire : > conanO'Brien = "It's a-me, Conan O'Brien!" @@ -317,8 +322,8 @@ des entiers et des caractères. Place à une liste !
-**Note :** Nous utilisons le mot-clé `let` pour définir un nom directement dans -GHCi. Écrire `let a = 1` dans GHCi est équivalent à écrire `a = 1` dans un +**Note :** Nous utilisons le mot-clé `let` pour définir un nom directement +dans GHCi. Écrire `let a = 1` dans GHCi est équivalent à écrire `a = 1` dans un script puis charger ce script.
@@ -349,9 +354,9 @@ réalisé à l'aide de l'opérateur `++`. Attention lors de l'utilisation répétée de l'opérateur `++` sur de longues chaînes. Lorsque vous accolez deux listes (et même si vous accolez une liste -singleton à une autre liste, par exemple : `[1, 2, 3] ++ [4]`), en interne, -Haskell doit parcourir la liste de gauche en entier. Ceci ne pose pas de -problème tant que les listes restent de taille raisonnable. Mais accoler une +singleton à une autre liste, par exemple : `[1, 2, 3] ++ [4]`), en +interne, Haskell doit parcourir la liste de gauche en entier. Ceci ne pose pas +de problème tant que les listes restent de taille raisonnable. Mais accoler une liste à la fin d'une liste qui contient cinquante millions d'éléments risque de prendre du temps. Cependant, placer quelque chose au début d'une liste en utilisant l'opérateur `:` (aussi appelé l'opérateur cons) est instantané. @@ -372,10 +377,10 @@ encore `2` devant, elle devient `[2, 3]`, etc.
-**Note :** `[]`, `[[]]` et `[[], [], []]` sont trois choses différentes. La -première est une liste vide, la deuxième est une liste qui contient un élément, -cet élément étant une liste vide, la troisième est une liste qui contient trois -éléments, qui sont tous des listes vides. +**Note :** `[]`, `[[]]` et `[[], [], []]` sont trois choses différentes. +La première est une liste vide, la deuxième est une liste qui contient un +élément, cet élément étant une liste vide, la troisième est une liste qui +contient trois éléments, qui sont tous des listes vides.
@@ -553,9 +558,9 @@ Texas rangées Que faire si l'on veut la liste des nombres de 1 à 20 ? Bien sûr, on pourrait les taper un par un, mais ce n'est pas une solution pour des gentlemen qui demandent l'excellence de leurs langages de programmation. Nous utiliserons -plutôt des progressions (NDT : qui sont appelées "ranges" en anglais, d'où le -titre de cette section). Les progressions sont un moyen de créer des listes qui -sont des suites arithmétiques d'éléments qui peuvent être énumérés. Les +plutôt des progressions (NDT : qui sont appelées "ranges" en anglais, d'où +le titre de cette section). Les progressions sont un moyen de créer des listes +qui sont des suites arithmétiques d'éléments qui peuvent être énumérés. Les nombres peuvent être énumérés. Un, deux, trois, quatre, etc. Les caractères peuvent aussi être énumérés. L'alphabet est une énumération des caractères de A à Z. Les noms ne peuvent pas être énumérés. Qu'est-ce qui suit "John" ? Je ne @@ -605,13 +610,13 @@ Vous pouvez aussi utiliser les progressions pour définir des listes infinies, simplement en ne précisant pas de borne supérieure. Nous verrons les détails des listes infinies un peu plus tard. Pour l'instant, examinons comment l'on pourrait obtenir les 24 premiers multiples de 13. Bien sûr, vous pourriez -écrire `[13, 26..24*13]`. Mais il y a une meilleure façon de faire : `take 24 -[13, 26..]`. Puisqu'Haskell est paresseux, il ne va pas essayer d'évaluer la +écrire `[13, 26..24*13]`. Mais il y a une meilleure façon de faire : `take +24 [13, 26..]`. Puisqu'Haskell est paresseux, il ne va pas essayer d'évaluer la liste infinie immédiatement et ne jamais terminer. Il va plutôt attendre de voir ce que vous voulez obtenir de cette liste infinie. Ici, il voit que vous ne voulez que les 24 premiers éléments, et il s'exécute poliment. -Une poignée de fonctions produit des listes infinies : +Une poignée de fonctions produit des listes infinies : `cycle` prend une liste et la cycle en une liste infinie. Si vous essayiez d'afficher le résultat, cela continuerait à jamais, donc il faut la couper @@ -693,7 +698,7 @@ est inclus dans la liste seulement si tous les prédicats sont évalués à `Tru > ["BOOM!","BOOM!","BANG!","BANG!"] On peut inclure plusieurs prédicats. Si nous voulions tous les nombres de 10 à -20 qui sont différents de 13, 15 et 19, on pourrait faire : +20 qui sont différents de 13, 15 et 19, on pourrait faire : > ghci> [ x | x <- [10..20], x /= 13, x /= 15, x /= 19] > [10,11,12,14,16,17,18,20] @@ -707,7 +712,7 @@ que l'on fournit. Une liste produite par une compréhension qui pioche dans deux listes de longueur 4 aura donc pour longueur 16, si tant est qu'on ne filtre pas d'élément. Si l'on a deux listes, `[2, 5, 10]` et `[8, 10, 11]`, et qu'on veut les produits possibles des combinaisons de deux nombres de chacune de ces -listes, voilà ce qu'on écrit : +listes, voilà ce qu'on écrit : > ghci> [ x*y | x <- [2,5,10], y <- [8,10,11]] > [16,20,22,40,50,55,80,100,110] @@ -736,14 +741,14 @@ que d'y donner un nom qu'on n'utilisera pas, on écrit `_`. Cette fonction remplace chaque élément de la liste par un `1`, et somme cette liste. La somme résultante sera donc la longueur de la liste. -Un rappel amical : puisque les chaînes de caractères sont des listes, on peut -utiliser les listes en compréhension pour traiter et produire des chaînes de -caractères. Voici une fonction qui prend une chaîne de caractères et supprime -tout sauf les caractères en majuscule. +Un rappel amical : puisque les chaînes de caractères sont des listes, on +peut utiliser les listes en compréhension pour traiter et produire des chaînes +de caractères. Voici une fonction qui prend une chaîne de caractères et +supprime tout sauf les caractères en majuscule. > removeNonUppercase st = [ c | c <- st, c `elem` ['A'..'Z']] -Testons : +Testons : > ghci> removeNonUppercase "Hahaha! Ahahaha!" > "HA" @@ -792,9 +797,9 @@ de nombres, mais ça n'a pas vraiment de sens. Alors qu'un tuple de taille deux (aussi appelé une paire) est un type propre, ce qui signifie qu'une liste ne peut pas avoir quelques paires puis un triplet (tuple de taille trois), utilisons donc cela en lieu et place. Plutôt que d'entourer nos vecteurs de -crochets, on utilise des parenthèses : `[(1, 2), (8, 11), (4, 5)]`. Et si l'on -essayait d'entrer la forme `[(1, 2), (8, 11, 5), (4, 5)]` ? Eh bien, on aurait -cette erreur : +crochets, on utilise des parenthèses : `[(1, 2), (8, 11), (4, 5)]`. Et si +l'on essayait d'entrer la forme `[(1, 2), (8, 11, 5), (4, 5)]` ? Eh bien, on +aurait cette erreur : > Couldn't match expected type `(t, t1)' > against inferred type `(t2, t3, t4)' @@ -808,7 +813,7 @@ liste `[(1, 2), ("One", 2)]` car le premier élément de cette liste est une paire de nombres alors que le second est une paire d'une chaîne de caractères et d'un nombre. Les tuples peuvent aussi être utilisés pour représenter un grand éventail de données. Par exemple, si nous voulions représenter le nom et -l'âge d'une personne en Haskell, on pourrait utiliser le triplet : +l'âge d'une personne en Haskell, on pourrait utiliser le triplet : `("Christopher", "Walken", 55)`. Comme dans l'exemple, les tuples peuvent aussi contenir des listes. @@ -826,7 +831,7 @@ seulement la valeur qu'il contient, et n'aurait donc pas d'intérêt. Comme les listes, les tuples peuvent être comparés entre eux si leurs composantes peuvent être comparées. Seulement, vous ne pouvez pas comparer des tuples de tailles différentes, alors que vous pouvez comparer des listes de -tailles différentes. Deux fonctions utiles qui opérent sur des paires : +tailles différentes. Deux fonctions utiles qui opérent sur des paires : `fst` prend une paire et renvoie sa première composante. @@ -844,13 +849,13 @@ tailles différentes. Deux fonctions utiles qui opérent sur des paires :
-**Note :** ces deux fonctions opèrent seulement sur des paires. Elles ne +**Note :** ces deux fonctions opèrent seulement sur des paires. Elles ne marcheront pas sur des triplets, des quadruplets, etc. Nous verrons comment extraire des données de tuples de différentes façons un peu plus tard.
-Une fonction cool qui produit une liste de paires : `zip`. Elle prend deux +Une fonction cool qui produit une liste de paires : `zip`. Elle prend deux listes et les zippe ensemble en joignant les éléments correspondants en des paires. C'est une fonction très simple, mais elle sert beaucoup. C'est particulièrement utile pour combiner deux listes d'une façon ou traverser deux @@ -872,17 +877,17 @@ se passe-t-il si les longueurs des listes ne correspondent pas ? La liste la plus longue est simplement coupée pour correspondre à la longueur de la plus courte. Puisqu'Haskell est paresseux, on peut zipper des listes -finies avec des listes infinies : +finies avec des listes infinies : > ghci> zip [1..] ["apple", "orange", "cherry", "mango"] > [(1,"apple"),(2,"orange"),(3,"cherry"),(4,"mango")] pythagore -Voici un problème qui combine tuples et listes en compréhensions : quel +Voici un problème qui combine tuples et listes en compréhensions : quel triangle rectangle a des côtés tous entiers, tous inférieurs ou égaux à 10, et a un périmètre de 24 ? Premièrement, essayons de générer tous les triangles -dont les côtés sont inférieurs ou égaux à 10 : +dont les côtés sont inférieurs ou égaux à 10 : > ghci> let triangles = [ (a,b,c) | c <- [1..10], b <- [1..10], a <- [1..10] ] @@ -909,13 +914,18 @@ transformations sur ces solutions et les filtrez jusqu'à obtenir les bonnes.
diff --git a/entrees-et-sorties.mkd b/entrees-et-sorties.mkd index 8560fd5..705e97d 100644 --- a/entrees-et-sorties.mkd +++ b/entrees-et-sorties.mkd @@ -3,13 +3,19 @@ @@ -73,7 +79,7 @@ la Linux pour Windows, autrement dit, juste ce qu'il vous faut.
-Pour commencer, tapez ceci dans votre éditeur de texte favori : +Pour commencer, tapez ceci dans votre éditeur de texte favori  > main = putStrLn "hello, world" @@ -83,7 +89,7 @@ comme on va le voir bientôt. Sauvegardez ce fichier comme `helloworld.hs`. Et maintenant, on va faire quelque chose sans précédent. On va réellement compiler notre programme ! Je suis tellement ému ! Ouvrez votre terminal et -naviguez jusqu'au répertoire où `helloworld.hs` est placé et faites : +naviguez jusqu'au répertoire où `helloworld.hs` est placé et faites  > $ ghc --make helloworld > [1 of 1] Compiling Main ( helloworld.hs, helloworld.o ) @@ -106,7 +112,7 @@ Examinons ce qu'on vient d'écrire. D'abord, regardons le type de la fonction > ghci> :t putStrLn "hello, world" > putStrLn "hello, world" :: IO () -On peut lire le type de `putStrLn` ainsi : `putStrLn` prend une chaîne de +On peut lire le type de `putStrLn` ainsi  `putStrLn` prend une chaîne de caractères et retourne une **action I/O** qui a pour type de retour `()` (c'est-à-dire le tuple vide, aussi connu comme *unit*). Une action I/O est quelque chose qui, lorsqu'elle sera exécutée, va effectuer une action avec des @@ -127,7 +133,7 @@ Donc, quand est-ce que cette action sera exécutée ? Eh bien, c'est ici que le Que votre programme entier ne soit qu'une action I/O semble un peu limitant. C'est pourquoi on peut utiliser la notation *do* pour coller ensemble plusieurs -actions I/O en une seule. Regardez l'exemple suivant : +actions I/O en une seule. Regardez l'exemple suivant  > main = do > putStrLn "Hello, what's your name?" @@ -160,7 +166,7 @@ Aha, o-kay. `getLine` est une action I/O qui contient un résultat de type `String`. Ça tombe sous le sens, parce qu'elle attendra que l'utilisateur tape quelque chose dans son terminal, et ensuite, cette frappe sera représentée comme une chaîne de caractères. Mais qu'est-ce qui se passe dans `name <- -getLine` alors ? Vous pouvez lire ceci ainsi : **effectue l'action I/O +getLine` alors ? Vous pouvez lire ceci ainsi  **effectue l'action I/O `getLine` puis lie la valeur résultante au nom `name`**. `getLine` a pour type `IO String`, donc `name` aura pour type `String`. Vous pouvez imaginer une action I/O comme une boîte avec des petits pieds qui sortirait dans le monde @@ -168,14 +174,14 @@ réel et irait faire quelque chose là-bas (comme des graffitis sur les murs) et reviendrait peut-être avec une valeur. Une fois qu'elle a attrapé une valeur pour vous, le seul moyen d'ouvrir la boîte pour en récupérer le contenu est d'utiliser la construction `<-`. Et si l'on sort une valeur d'une action I/O, -on ne peut le faire qu'à l'intérieur d'une autre action I/O. C'est ainsi -qu'Haskell parvient à séparer proprement les parties pure et impure du code. -`getLine` est en un sens impure, parce que sa valeur de retour n'est pas -garantie d'être la même lorsqu'on l'exécute deux fois. C'est pourquoi elle est -en quelque sorte *tachée* par le constructeur de types `IO`, et on ne peut -récupérer cette donnée que dans du code I/O. Et puisque le code I/O est taché -aussi, tout calcul qui dépend d'une valeur tachée I/O renverra un résultat -taché. + on ne peut le faire qu'à l'intérieur d'une autre action I/O. C'est ainsi + qu'Haskell parvient à séparer proprement les parties pure et impure du + code. `getLine` est en un sens impure, parce que sa valeur de retour n'est + pas garantie d'être la même lorsqu'on l'exécute deux fois. C'est pourquoi + elle est en quelque sorte *tachée* par le constructeur de types `IO`, et on + ne peut récupérer cette donnée que dans du code I/O. Et puisque le code I/O + est taché aussi, tout calcul qui dépend d'une valeur tachée I/O renverra un + résultat taché. Quand je dis *taché*, je ne veux pas dire taché de façon à ce que l'on ne puisse plus jamais utiliser le résultat contenu dans l'action I/O dans un code @@ -183,8 +189,8 @@ pur. Non, on dé-tache temporairement la donnée dans l'action I/O lorsqu'on la lie à un nom. Quand on fait `name <- getLine`, `name` est une chaîne de caractères normale, parce qu'elle représente ce qui est dans la boîte. On peut avoir une fonction très compliquée qui, mettons, prend votre nom (une chaîne de -caractères normale) et un paramètre, et vous donne votre fortune et votre futur -basé sur votre nom. On peut faire cela : + caractères normale) et un paramètre, et vous donne votre fortune et +votre futur basé sur votre nom. On peut faire cela  > main = do > putStrLn "Hello, what's your name?" @@ -213,7 +219,7 @@ des morts-vivants, et il est dans notre meilleur intérêt de restreindre les parties I/O de notre code autant que faire se peut. Chaque action I/O effectuée encapsule son résultat en son sein. C'est pourquoi -notre précédent programme aurait pu s'écrire ainsi : +notre précédent programme aurait pu s'écrire ainsi  > main = do > foo <- putStrLn "Hello, what's your name?" @@ -261,13 +267,14 @@ GHCi et qu'on tape Entrée, il va l'évaluer (autant que nécessaire) et appeler terminal en appelant implicitement `putStrLn`. Vous vous souvenez des liaisons *let* ? Si ce n'est pas le cas, -rafraîchissez-vous l'esprit en lisant [cette -section](syntaxe-des-fonctions#let-it-be). Elles sont de la forme `let bindings -in expression`, où `bindings` sont les noms à donner aux expressions et -`expression` est l'expression évaluée qui peut voir ces liaisons. On a aussi -dit que dans les listes en compréhension, la partie *in* n'était pas -nécessaire. Eh bien, on peut aussi les utiliser dans les blocs *do* comme on le -faisait dans les listes en compréhension. Regardez : + rafraîchissez-vous l'esprit en lisant [cette + section](syntaxe-des-fonctions#let-it-be). Elles sont de la forme `let + bindings in expression`, où `bindings` sont les noms à donner aux + expressions et `expression` est l'expression évaluée qui peut voir ces + liaisons. On a aussi dit que dans les listes en compréhension, la partie + *in* n'était pas nécessaire. Eh bien, on peut aussi les utiliser dans les + blocs *do* comme on le faisait dans les listes en compréhension. + Regardez  > import Data.Char > @@ -300,7 +307,7 @@ avait fait quelque chose comme `let firstName = getLine`, on aurait juste donné À présent, on va faire un programme qui lit continuellement une ligne et l'affiche avec les mots renversés. Le programme s'arrêtera si on entre une -ligne vide. Voici le programme : +ligne vide. Voici le programme  > main = do > line <- getLine @@ -318,10 +325,11 @@ s'intéresse au code.
-**Astuce** : Pour lancer un programme, vous pouvez soit le compiler puis lancer -l'exécutable produit en faisant `ghc --make helloworld` puis `./helloworld`, ou -bien vous pouvez utiliser la commande `runhaskell` ainsi : `runhaskell -helloworld.hs` et votre programme sera exécuté à la volée. +**Astuce**  Pour lancer un programme, vous pouvez soit le compiler puis +lancer l'exécutable produit en faisant `ghc --make helloworld` puis +`./helloworld`, ou bien vous pouvez utiliser la commande `runhaskell` +ainsi  `runhaskell helloworld.hs` et votre programme sera exécuté à la +volée.
@@ -345,7 +353,7 @@ C'est pour cela que dans des blocs *do* I/O, les *if* doivent avoir la forme Regardons d'abord du côté de la clause *else*. Puisqu'on doit avoir une action I/O après le *else*, on crée un bloc *do* pour coller des actions en une. Vous -pouvez aussi écrire cela : +pouvez aussi écrire cela  > else (do > putStrLn $ reverseWords line @@ -362,25 +370,26 @@ Maintenant, que se passe-t-il lorsque `null line` est vrai ? Dans ce cas, ce qui suit le *then* est exécuté. Si on regarde, on voit qu'il y a `then return ()`. Si vous avez utilisé des langages impératifs comme C, Java ou Python, vous vous dites certainement que vous savez déjà ce que `return` fait, et il se peut -que vous ayez déjà sauté ce long paragraphe. Eh bien, voilà le détail qui tue : -**le `return` de Haskell n'a vraiment rien à voir avec le `return` de la -plupart des autres langages !** Il a le même nom, ce qui embrouille beaucoup de -monde, mais en réalité, il est bien différent. Dans les langages impératifs, -`return` termine généralement l'exécution de la méthode ou sous-routine, en -rapportant une valeur à son appelant. En Haskell (plus spécifiquement, dans les -action I/O), il crée une action I/O à partir d'une valeur pure. Si vous -repensez à l'analogie de la boîte faite précédemment, il prend une valeur et la -met dans une boîte. L'action I/O résultante ne fait en réalité rien, mais -encapsule juste cette valeur comme son résultat. Ainsi, dans un contexte d'I/O, -`return "HAHA"` aura pour type `IO String`. Quel est le but de transformer une -valeur pure en une action I/O qui ne fait rien ? Pourquoi salir notre programme -d'`IO` plus que nécessaire ? Eh bien, il nous fallait une action I/O à exécuter -dans le cas où la ligne en entrée était vide. C'est pourquoi on a créé une -fausse action I/O qui ne fait rien, en écrivant `return ()`. +que vous ayez déjà sauté ce long paragraphe. Eh bien, voilà le détail qui +tue  **le `return` de Haskell n'a vraiment rien à voir avec le `return` de +la plupart des autres langages !** Il a le même nom, ce qui embrouille beaucoup +de monde, mais en réalité, il est bien différent. Dans les langages impératifs, + `return` termine généralement l'exécution de la méthode ou sous-routine, en + rapportant une valeur à son appelant. En Haskell (plus spécifiquement, dans + les action I/O), il crée une action I/O à partir d'une valeur pure. + Si vous repensez à l'analogie de la boîte faite précédemment, il prend une + valeur et la met dans une boîte. L'action I/O résultante ne fait en réalité + rien, mais encapsule juste cette valeur comme son résultat. Ainsi, dans un + contexte d'I/O, `return "HAHA"` aura pour type `IO String`. Quel est le but + de transformer une valeur pure en une action I/O qui ne fait rien ? Pourquoi + salir notre programme d'`IO` plus que nécessaire ? Eh bien, il nous fallait + une action I/O à exécuter dans le cas où la ligne en entrée était vide. + C'est pourquoi on a créé une fausse action I/O qui ne fait rien, en écrivant + `return ()`. Utiliser `return` ne cause pas la fin de l'exécution du bloc *do* I/O ou quoi que ce soit du genre. Par exemple, ce programme va gentiment s'exécuter -jusqu'au bout de la dernière ligne : +jusqu'au bout de la dernière ligne  > main = do > return () @@ -402,9 +411,9 @@ avec `<-` pour lier des choses à des noms. Comme vous voyez, `return` est un peu l'opposé de `<-`. Alors que `return` prend une valeur et l'enveloppe dans une boîte, `<-` prend une boîte, -l'exécute, et en extrait la valeur pour la lier à un nom. Faire cela est un peu -redondant, puisque l'on dispose des liaisons *let* dans les blocs *do*, donc on -préfère : + l'exécute, et en extrait la valeur pour la lier à un nom. Faire cela est + un peu redondant, puisque l'on dispose des liaisons *let* dans les blocs + *do*, donc on préfère  > main = do > let a = "hell" @@ -525,7 +534,7 @@ pas Entrée. Ce programme semble lire un caractère et vérifier si c'est un espace. Si c'est le cas, il s'arrête, sinon il affiche le caractère et recommence. C'est un peu -ce qu'il fait, mais pas forcément comme on s'y attend. Regardez : +ce qu'il fait, mais pas forcément comme on s'y attend. Regardez  > $ runhaskell getchar_test.hs > hello sir @@ -538,13 +547,13 @@ il agit sur ce qu'on vient de taper. Essayez de jouer avec ce programme pour vous rendre compte ! La fonction `when` est dans `Control.Monad` (pour l'utiliser, faites `import -Control.Monad`). Elle est intéressante parce que, dans un bloc *do*, on dirait -qu'elle contrôle le flot du programme, alors qu'en fait c'est une fonction tout -à fait normale. Elle prend une valeur booléenne et une action I/O. Si la valeur -booléenne est `True`, elle retourne l'action I/O qu'on lui a passée. Si c'est -`False`, elle retourne `return ()`, donc une action I/O qui ne fait rien. Voici -comment on pourrait réécrire le code précédent dans lequel on présentait -`getChar`, en utilisant `when` : + Control.Monad`). Elle est intéressante parce que, dans un bloc *do*, on +dirait qu'elle contrôle le flot du programme, alors qu'en fait c'est une +fonction tout à fait normale. Elle prend une valeur booléenne et une action +I/O. Si la valeur booléenne est `True`, elle retourne l'action I/O qu'on lui a +passée. Si c'est `False`, elle retourne `return ()`, donc une action I/O qui ne +fait rien. Voici comment on pourrait réécrire le code précédent dans lequel on +présentait `getChar`, en utilisant `when`  > import Control.Monad > @@ -621,7 +630,7 @@ de nos actions séquencées. `forever` prend une action I/O et retourne une action I/O qui répète la première indéfiniment. Elle est dans `Control.Monad`. Ce programme va demander -indéfiniment à l'utilisateur une entrée, et va la recracher en MAJUSCULES : +indéfiniment à l'utilisateur une entrée, et va la recracher en MAJUSCULES  > import Control.Monad > import Data.Char @@ -635,7 +644,7 @@ indéfiniment à l'utilisateur une entrée, et va la recracher en MAJUSCULES : l'ordre inverse. Le premier paramètre est la liste, et le second la fonction à mapper sur cette liste, qui sera finalement séquencée. Pourquoi est-ce utile ? Eh bien, en étant un peu créatif sur l'utilisation des lambdas et de la -notation *do*, on peut faire des choses comme : +notation *do*, on peut faire des choses comme  > import Control.Monad > @@ -661,11 +670,11 @@ résultat à `colors`. `colors` est une liste tout ce qu'il y a de plus normale, qui contient des chaînes de caractères. À la fin, on affiche toutes ces couleurs en faisant `mapM putStrLn colors`. -Vous pouvez imaginer `forM` comme signifiant : crée une action I/O pour tous -les éléments de cette liste. Ce que l'action I/O fait peut dépendre de la +Vous pouvez imaginer `forM` comme signifiant  crée une action I/O pour +tous les éléments de cette liste. Ce que l'action I/O fait peut dépendre de la valeur de l'élément de la liste à partir duquelle elle a été créée. Finalement, -exécute toutes ces actions et lie leur résultat à quelque chose. Notez qu'on -n'est pas forcé de le lier, on pourrait juste le jeter. + exécute toutes ces actions et lie leur résultat à quelque chose. Notez + qu'on n'est pas forcé de le lier, on pourrait juste le jeter. > $ runhaskell form_test.hs > Which color do you associate with the number 1? @@ -718,13 +727,13 @@ rencontrer un caractère de fin de fichier. Son type est `getContents :: IO String`. Ce qui est cool avec `getContents`, c'est qu'elle effectue une entrée paresseuse. Quand on fait `foo <- getContents`, elle ne va pas lire toute l'entrée d'un coup, la stocker en mémoire, puis la lier à `foo`. Non, elle est -paresseuse ! Elle dira : "_Ouais ouais, je lirai l'entrée du terminal plus +paresseuse ! Elle dira  "_Ouais ouais, je lirai l'entrée du terminal plus tard, quand tu en auras besoin !_". `getContents` est très utile quand on veut connecter la sortie d'un programme à l'entrée de notre programme. Si vous ne savez pas comment cette connexion (à -base de *tubes*) fonctionne dans les systèmes type Unix, voici un petit aperçu. -Créons un fichier texte qui contient ce haiku : + base de *tubes*) fonctionne dans les systèmes type Unix, voici un petit +aperçu. Créons un fichier texte qui contient ce haiku  > I'm a lil' teapot > What's with that airplane food, huh? @@ -736,7 +745,7 @@ haikus, je suis preneur. Bien, souvenez-vous de ce programme qu'on a écrit en introduisant la fonction `forever`. Il demandait à l'utilisateur une ligne, et lui retournait en MAJUSCULES, puis recommençait, indéfiniment. Pour vous éviter de remonter tout -là-haut, je vous la remets ici : +là-haut, je vous la remets ici  > import Control.Monad > import Data.Char @@ -765,16 +774,16 @@ qui affiche le fichier donné en argument. Regardez-moi ça, booyaka ! > capslocker : hGetLine: end of file Comme vous voyez, connecter la sortie d'un programme (dans notre cas c'était -*cat*) à l'entrée d'un autre (ici *capslocker*) est fait à l'aide du caractère -`|`. Ce qu'on a fait ici est à peu près équivalent à lancer *capslocker*, puis -taper notre haiku dans le terminal, avant d'envoyer le caractère de fin de -fichier (généralement en tapant Ctrl-D). C'est comme lancer *cat haiku.txt* et -dire : "Attends, n'affiche pas ça dans le terminal, va le dire à *capslocker* -plutôt !". + *cat*) à l'entrée d'un autre (ici *capslocker*) est fait à l'aide du +caractère `|`. Ce qu'on a fait ici est à peu près équivalent à lancer +*capslocker*, puis taper notre haiku dans le terminal, avant d'envoyer le +caractère de fin de fichier (généralement en tapant Ctrl-D). C'est comme lancer +*cat haiku.txt* et dire  "Attends, n'affiche pas ça dans le terminal, va +le dire à *capslocker* plutôt !". Donc ce qu'on fait principalement avec cette utilisation de `forever` c'est prendre l'entrée et la transformer en une sortie. C'est pourquoi on peut -utiliser `getContents` pour rendre ce programme encore plus court et joli : +utiliser `getContents` pour rendre ce programme encore plus court et joli  > import Data.Char > @@ -812,18 +821,18 @@ une vraie chaîne de caractères, mais plutôt comme une promesse de produire cette chaîne de caractères en temps voulu. Quand on mappe `toUpper` sur `contents`, c'est aussi une promesse de mapper la fonction sur le contenu une fois qu'il sera disponible. Et finalement, quand `putStr` est exécuté, il dit à -la promesse précédente : "_Hé, j'ai besoin des lignes en majuscules !_". -Celle-ci n'a pas encore les lignes, alors elle demande à `contents` : "_Hé, -pourquoi tu n'irais pas chercher ces lignes dans le terminal à présent ?_". -C'est à ce moment que `getContents` va vraiment lire le terminal et donner une -ligne au code qui a demandé d'avoir quelque chose de tangible. Ce code mappe -alors `toUpper` sur la ligne et la donne à `putStr`, qui l'affiche. Puis -`putStr` dit : "_Hé, j'ai besoin de la ligne suivante, allez !_" et tout ceci -se répète jusqu'à ce qu'il n'y ait plus d'entrée, comme l'indique le caractère -de fin de fichier. +la promesse précédente  "_Hé, j'ai besoin des lignes en majuscules !_". +Celle-ci n'a pas encore les lignes, alors elle demande à `contents`  "_Hé, + pourquoi tu n'irais pas chercher ces lignes dans le terminal à présent ?_". + C'est à ce moment que `getContents` va vraiment lire le terminal et donner + une ligne au code qui a demandé d'avoir quelque chose de tangible. Ce code + mappe alors `toUpper` sur la ligne et la donne à `putStr`, qui l'affiche. + Puis `putStr` dit  "_Hé, j'ai besoin de la ligne suivante, allez !_" + et tout ceci se répète jusqu'à ce qu'il n'y ait plus d'entrée, comme + l'indique le caractère de fin de fichier. Faisons un programme qui prend une entrée et affiche seulement les lignes qui -font moins de 10 caractères de long. Observez : +font moins de 10 caractères de long. Observez  > main = do > contents <- getContents @@ -842,15 +851,15 @@ l'entrée, on peut l'implémenter en lisant le contenu d'entrée, puis en exécutant une fonction pure sur ce contenu, et en affichant le résultat que la fonction a renvoyé. -La fonction `shortLinesOnly` fonctionne ainsi : elle prend une chaîne de +La fonction `shortLinesOnly` fonctionne ainsi  elle prend une chaîne de caractères comme "short`nlooooooooooooooong\nshort again"`. Cette chaîne a trois lignes, dont deux courtes et une au milieu plus longue. La fonction lance `lines` sur cette chaîne, ce qui la convertit en `["short", -"looooooooooooooong", "short again"]`, qu'on lie au nom `allLines`. Cette liste -de chaînes est ensuite filtrée pour ne garder que les lignes de moins de 10 -caractères, produisant `["short", "short again"]`. Et finalement, `unlines` -joint cette liste en une seule chaîne, donnant `"short\nshort again"`. Testons -cela. + "looooooooooooooong", "short again"]`, qu'on lie au nom `allLines`. Cette + liste de chaînes est ensuite filtrée pour ne garder que les lignes de moins + de 10 caractères, produisant `["short", "short again"]`. Et finalement, + `unlines` joint cette liste en une seule chaîne, donnant `"short\nshort + again"`. Testons cela. > i'm short > so am i @@ -872,11 +881,11 @@ On connecte le contenu de *shortlines.txt* à l'entrée de *shotlinesonly* et en sortie, on obtient les lignes courtes. Ce motif de récupérer une chaîne en entrée, la transformer avec une fonction, -puis écrire le résultat est tellement commun qu'il existe une fonction qui rend -cela encore plus simple, appelée `interact`. `interact` prend une fonction de -type `String -> String` en paramètre et retourne une action I/O qui va lire une -entrée, lancer cette fonction dessus, et afficher le résultat. Modifions notre -programme en conséquence : + puis écrire le résultat est tellement commun qu'il existe une fonction qui + rend cela encore plus simple, appelée `interact`. `interact` prend une + fonction de type `String -> String` en paramètre et retourne une action I/O + qui va lire une entrée, lancer cette fonction dessus, et afficher le + résultat. Modifions notre programme en conséquence  > main = interact shortLinesOnly > @@ -924,11 +933,11 @@ Plutôt direct. D'abord, on change quelque chose comme `"elephant\nABCBA\nwhatever"` en `["elephant", "ABCBA", "whatever"]`, puis on mappe la lambda sur ça, donnant `["not a palindrome", "palindrome", "not a palindrome"]` et enfin `unlines` joint cette liste en une seule chaîne de -caractères. À présent, on peut faire : +caractères. À présent, on peut faire  > main = interact respondPalindromes -Testons ça : +Testons ça  > $ runhaskell palindromes.hs > hehe @@ -947,7 +956,7 @@ l'afficher en sortie. On termine le programme en envoyant un caractère de fin de fichier. On peut aussi utiliser ce programme en connectant simplement un fichier en -entrée. Disons qu'on ait ce fichier : +entrée. Disons qu'on ait ce fichier  > dogaroo > radar @@ -955,7 +964,7 @@ entrée. Disons qu'on ait ce fichier : > madam et qu'on le sauvegarde comme `words.txt`. Voici ce qu'on obtient en le -connectant en entrée de notre programme : +connectant en entrée de notre programme  > $ cat words.txt | runhaskell palindromes.hs > not a palindrome @@ -986,14 +995,15 @@ dans l'entrée ou la sortie standard. On va commencer avec un programme très simple qui ouvre un fichier nommé *girlfriend.txt*, qui contient un vers du tube d'Avril Lavigne numéro 1 -*Girlfriend*, et juste afficher cela dans le terminal. Voici *girlfriend.txt* : +*Girlfriend*, et juste afficher cela dans le terminal. Voici +*girlfriend.txt*  > Hey! Hey! You! You! > I don't like your girlfriend! > No way! No way! > I think you need a new one! -Et voici notre programme : +Et voici notre programme  > import System.IO > @@ -1003,7 +1013,7 @@ Et voici notre programme : > putStr contents > hClose handle -En le lançant, on obtient le résultat attendu : +En le lançant, on obtient le résultat attendu  > $ runhaskell girlfriend.hs > Hey! Hey! You! You! @@ -1020,18 +1030,18 @@ quatrième ligne suggère que nous devrions chercher une nouvelle petite amie. Hum, regardons plutôt le programme ligne par ligne ! Notre programme consiste en plusieurs actions I/O combinées ensemble par un bloc *do*. Dans la première ligne du bloc *do*, on remarque une fonction nouvelle nommée *openFile*. Voici -sa signature de type : `openFile :: FilePath -> IOMode -> OI Handle`. Si vous -lisez ceci tout haut, cela donne : `openFile` prend un chemin vers un fichier -et un `IOMode` et retourne une action I/O qui va ouvrir le fichier et encapsule -pour résultat la poignée vers le fichier associé. +sa signature de type  `openFile :: FilePath -> IOMode -> OI Handle`. Si +vous lisez ceci tout haut, cela donne  `openFile` prend un chemin vers un +fichier et un `IOMode` et retourne une action I/O qui va ouvrir le fichier et +encapsule pour résultat la poignée vers le fichier associé. `FilePath` est juste un [synonyme du type](creer-nos-propres-types-et-classes-de-types#synonymes-de-types) `String`, -simplement défini comme : + simplement défini comme  > type FilePath = String -`IOMode` est défini ainsi : +`IOMode` est défini ainsi  > data IOMode = ReadMode | WriteMode | AppendMode | ReadWriteMode @@ -1086,7 +1096,7 @@ ouvre le fichier, applique notre fonction, puis ferme le fichier. Le résultat retourné par cette action I/O est le même que le résultat retourné par la fonction qu'on lui fournit. Ça peut vous sembler compliqué, mais c'est en fait très simple, surtout avec des lambdas, voici le précédent exemple réécrit avec -`withFile` : +`withFile`  > import System.IO > @@ -1103,7 +1113,7 @@ directement une action I/O, est que l'action I/O ne saurait pas sur quel fichier elle doit agir autrement. Ainsi, `withFile` ouvre le fichier et passe la poignée à la fonction qu'on lui a donnée. Elle récupère ainsi une action I/O, et crée à son tour une action I/O qui est comme la précédente mais ferme -le fichier à la fin. Voici comment coder notre propre fonction `withFile` : +le fichier à la fin. Voici comment coder notre propre fonction `withFile`  > withFile' :: FilePath -> IOMode -> (Handle -> IO a) -> IO a > withFile' path mode f = do @@ -1130,8 +1140,8 @@ Tout comme on a `hGetContents` qui fonctionne comme `getContents` mais pour un fichier, il y a aussi `hGetLine`, `hPutStr`, `hPutStrLn`, `hGetChar`, etc. Elles fonctionnent toutes comme leur équivalent sans h, mais prennent une poignée en paramètre et opèrent sur le fichier correspondant plutôt que -l'entrée ou la sortie standard. Par exemple : `putStrLn` est une fonction qui -prend une chaîne de caractères et retourne une action I/O qui affiche cette +l'entrée ou la sortie standard. Par exemple  `putStrLn` est une fonction +qui prend une chaîne de caractères et retourne une action I/O qui affiche cette chaîne dans le terminal avec un retour à la ligne à la fin. `hPutStrLn` prend une poignée et une chaîne et retourne une action I/O qui écrit cette chaîne dans le fichier associé à la poignée, suivie d'un retour à la ligne. Dans la @@ -1139,14 +1149,16 @@ même veine, `hGetLine` prend une poignée et retourne une action I/O qui lit un ligne de ce fichier. Charger des fichiers et traiter leur contenu comme des chaînes de caractères -est tellement commun qu'on a ces trois fonctions qui facilitent le travail : +est tellement commun qu'on a ces trois fonctions qui facilitent le +travail  `readFile` a pour signature `readFile :: FilePath -> IO String`. Souvenez-vous que `FilePath` est juste un nom plus joli pour `String`. `readFile` prend un chemin vers un fichier et retourne une action I/O qui lit ce fichier (paresseusement bien sûr) et lie son contenu à une chaîne de caractères. C'est généralement plus pratique que de faire `openFile`, de lier le retour à une -poignée, puis de faire `hGetContents`. Ainsi, le précédent exemple se réécrit : +poignée, puis de faire `hGetContents`. Ainsi, le précédent exemple se +réécrit  > import System.IO > @@ -1208,7 +1220,7 @@ pas de caractère pour aller à la ligne à la fin. Ooh, encore une chose. On a dit que `contents <- hGetContents handle` ne lisait pas tout le fichier d'un coup pour le stocker en mémoire. C'est une -entrée-sortie paresseuse, donc faire : +entrée-sortie paresseuse, donc faire  > main = do > withFile "something.txt" ReadMode (\handle -> do @@ -1231,12 +1243,12 @@ comme une taille cool. Vous pouvez contrôler comment la mise en tampon est effectuée en utilisant la fonction `hSetBuffering`. Elle prend une poignée et un `BufferMode` et retourne une action I/O qui définit le mode de mise en tampon. `BufferMode` est un -simple type de données énuméré, et les valeurs possibles sont : `NoBuffering`, -`LineBuffering` ou `BlockBuffering (Maybe Int)`. Le `Maybe Int` permet de -préciser la taille des blocs, en octets. Si c'est `Nothing`, alors le système -d'exploitation détermine la taille du bloc. `NoBuffering` signifie que les -caractères sont lus un par un. `NoBuffering` n'est généralement pas efficace, -parce qu'il doit accéder le disque tout le temps. +simple type de données énuméré, et les valeurs possibles sont  +`NoBuffering`, `LineBuffering` ou `BlockBuffering (Maybe Int)`. Le `Maybe Int` +permet de préciser la taille des blocs, en octets. Si c'est `Nothing`, alors le +système d'exploitation détermine la taille du bloc. `NoBuffering` signifie que +les caractères sont lus un par un. `NoBuffering` n'est généralement pas +efficace, parce qu'il doit accéder le disque tout le temps. Voici notre code précédent, sauf qu'il ne lit pas ligne par ligne, mais en blocs de 2048 octets. @@ -1261,8 +1273,8 @@ rapporte toutes les données qu'il a lues jusqu'ici. Mais on peut utiliser Après avoir vidé le tampon en écriture, les données sont disponibles pour n'importe quel autre programme qui accède au fichier en lecture en même temps. -Pensez à la mise en tampon par bloc comme cela : votre cuvette de toilettes est -faite pour se vider dès qu'elle contient un litre d'eau. Vous pouvez donc +Pensez à la mise en tampon par bloc comme cela  votre cuvette de toilettes +est faite pour se vider dès qu'elle contient un litre d'eau. Vous pouvez donc commencer à la remplir d'eau, et dès que vous atteignez un litre, l'eau est automatiquement vidée, et les données que vous aviez mises dans cette eau sont lues. Mais vous pouvez aussi vider la cuvette manuellement en actionnant la @@ -1279,7 +1291,7 @@ pour que vous voyiez que c'est très simple. On va utiliser quelques nouvelles fonctions sorties de `System.Directory` et une nouvelle fonction de `System.IO`, mais j'expliquerai cela en temps voulu. -Bon, voici le programme qui retire un élément de *todo.txt* : +Bon, voici le programme qui retire un élément de *todo.txt*  > import System.IO > import System.Directory @@ -1314,10 +1326,11 @@ fichier temporaire. On a utilisé `"."` pour le dossier temporaire, parce que d'exploitation. On a utilisé `"temp"` pour le préfixe de nom du fichier temporaire, ce qui signifie que le fichier sera nommé *temp* suivi de caractères aléatoires. Cette fonction retourne une action I/O qui crée le -fichier temporaire, et le résultat de cette action est une paire : le nom du -fichier créé et sa poignée. On pouvait simplement ouvrir un fichier *todo2.txt* -ou quelque chose comme ça, mais il vaut mieux ouvrir un fichier temporaire à -l'aide d'`openTempFile` pour être certain de ne pas en écraser un autre. +fichier temporaire, et le résultat de cette action est une paire  le nom +du fichier créé et sa poignée. On pouvait simplement ouvrir un fichier +*todo2.txt* ou quelque chose comme ça, mais il vaut mieux ouvrir un fichier +temporaire à l'aide d'`openTempFile` pour être certain de ne pas en écraser un +autre. On n'a pas utilisé `getCurrentDirectory` pour récupérer le dossier courant avant de le passer à `openTempFile` parce que `.` indique le répertoire courant @@ -1407,7 +1420,7 @@ cette approche quand on a voulu savoir quel élément l'utilisateur voulait supprimer. Ça marche, mais ce n'est pas optimal, parce que cela requiert que l'utilisateur lance le programme, attende que le programme lui demande quelque chose, et ensuite indique le fichier au programme. Ceci est appelé un programme -interactif, et le problème est le suivant : que faire si l'on souhaite +interactif, et le problème est le suivant  que faire si l'on souhaite automatiser l'exécution du programme, comme avec un script ? Il est plus dur de faire un script qui interagit avec un programme que de faire un script qui appelle un ou même plusieurs programmes. @@ -1424,7 +1437,7 @@ les arguments du programme et les encapsuler dans son résultat sous forme d'une liste. `getProgName` a pour type `getProgName :: IO String` et est une action I/O qui contient le nom du programme. -Voici un petit programme qui démontre leur fonctionnement : +Voici un petit programme qui démontre leur fonctionnement  > import System.Environment > import Data.List @@ -1459,7 +1472,7 @@ fera dépendra des arguments. On va aussi le faire de manière à ce qu'il puiss opérer sur différents fichiers, pas seulement *todo.txt*. On va l'appeler *todo* ("à faire") et il servira à faire (haha !) trois choses -différentes : +différentes  * Voir des tâches * Ajouter des tâches @@ -1492,7 +1505,7 @@ etc. > ] Il nous reste encore à définir `main`, `add`, `view` et `remove`, commençons -par `main` : +par `main`  > main = do > (command:args) <- getArgs @@ -1522,7 +1535,7 @@ monkey"]`) et retournera une action I/O qui ajoute `Spank the monkey` à *todo.txt*. Génial ! Il ne reste plus qu'à implémenter `add`, `view` et `remove`. -Commençons par `add` : +Commençons par `add`  > add :: [String] -> IO () > add [fileName, todoItem] = appendFile fileName (todoItem ++ "\n") @@ -1620,9 +1633,9 @@ Voilà le programme final en entier, dans toute sa splendeur ! salade -Pour résumer notre solution : on a fait une liste associative de résolution qui -associe à chaque commande une fonction qui prend des arguments de la ligne de -commande et retourne une action I/O. On regarde ce qu'est la commande, et en +Pour résumer notre solution  on a fait une liste associative de résolution +qui associe à chaque commande une fonction qui prend des arguments de la ligne +de commande et retourne une action I/O. On regarde ce qu'est la commande, et en fonction de ça on résout l'appel sur la fonction appropriée de la liste de résolution. On appelle cette fonction avec le reste des arguments pour obtenir une action I/O qui fera l'action attendue et ensuite on exécute cette action ! @@ -1686,16 +1699,17 @@ quasiment aléatoires. Dans la plupart des langages de programmation, vous avez des fonctions qui vous donnent un nombre aléatoire. Chaque fois que vous appelez cette fonction, vous obtenez (on l'espère) un nombre aléatoire différent. Quid d'Haskell ? Eh bien, -souvenez-vous, Haskell est un langage fonctionnel pur. Cela signifie qu'il a la -propriété de transparence référentielle. Ce que CELA signifie, c'est qu'une -fonction à laquelle on donne les mêmes paramètres plusieurs fois retournera -toujours le même résultat. C'est très cool, parce que ça nous permet de -raisonner différemment à propos de nos programmes, et de retarder l'évaluation -jusqu'à ce qu'elle soit nécessaire. Si j'appelle une fonction, je peux être -certain qu'elle ne va pas faire des choses folles avant de me donner son -résultat. Tout ce qui importe, c'est son résultat. Cependant, cela rend un peu -difficile les choses dans le cas des nombres aléatoires. Si j'ai une fonction -comme ça : + souvenez-vous, Haskell est un langage fonctionnel pur. Cela signifie + qu'il a la propriété de transparence référentielle. Ce que CELA + signifie, c'est qu'une fonction à laquelle on donne les mêmes + paramètres plusieurs fois retournera toujours le même résultat. C'est + très cool, parce que ça nous permet de raisonner différemment à propos + de nos programmes, et de retarder l'évaluation jusqu'à ce qu'elle soit + nécessaire. Si j'appelle une fonction, je peux être certain qu'elle ne + va pas faire des choses folles avant de me donner son résultat. Tout ce + qui importe, c'est son résultat. Cependant, cela rend un peu difficile + les choses dans le cas des nombres aléatoires. Si j'ai une fonction + comme ça  > randomNumber :: (Num a) => a > randomNumber = 4 @@ -1718,19 +1732,19 @@ un autre type de données). Pénétrez dans le module `System.Random`. Il a toutes les fonctions qui satisfont notre besoin d'aléatoire. Plongeons donc dans l'une des fonctions -qu'il exporte, j'ai nommé `random`. Voici son type : `random :: (RandomGen g, -Random a) => g -> (a, g)`. Ouah ! Que de nouvelles classes de types dans cette -déclaration ! La classe `RandomGen` est pour les types qui peuvent agir comme -des sources d'aléatoire. La classe de types `Random` est pour les choses qui -peuvent avoir une valeur aléatoire. Un booléen peut prendre une valeur -aléatoire, soit `True` soit `False`. Un nombre peut prendre une pléthore de -différentes valeurs aléatoires. Est-ce qu'une fonction peut prendre une valeur -aléatoire ? Je ne pense pas, probablement pas ! Si l'on essaie de traduire la -déclaration de `random`, cela donne quelque chose comme : elle prend un -générateur aléatoire (c'est notre source d'aléatoire) et retourne une valeur -aléatoire et un nouveau générateur aléatoire. Pourquoi retourne-t-elle un -nouveau générateur en plus de la valeur aléatoire ? Eh bien, on va voir ça dans -un instant. +qu'il exporte, j'ai nommé `random`. Voici son type  `random :: (RandomGen + g, Random a) => g -> (a, g)`. Ouah ! Que de nouvelles classes de types +dans cette déclaration ! La classe `RandomGen` est pour les types qui peuvent +agir comme des sources d'aléatoire. La classe de types `Random` est pour les +choses qui peuvent avoir une valeur aléatoire. Un booléen peut prendre une +valeur aléatoire, soit `True` soit `False`. Un nombre peut prendre une pléthore +de différentes valeurs aléatoires. Est-ce qu'une fonction peut prendre une +valeur aléatoire ? Je ne pense pas, probablement pas ! Si l'on essaie de +traduire la déclaration de `random`, cela donne quelque chose comme  elle +prend un générateur aléatoire (c'est notre source d'aléatoire) et retourne une +valeur aléatoire et un nouveau générateur aléatoire. Pourquoi retourne-t-elle +un nouveau générateur en plus de la valeur aléatoire ? Eh bien, on va voir ça +dans un instant. Pour utiliser notre fonction `random`, il nous faut obtenir un de ces générateurs aléatoires. Le module `System.Random` exporte un type cool, @@ -1835,7 +1849,7 @@ basées sur ce générateur. > [7.904789e-2,0.62691015,0.26363158,0.12223756,0.38291094] Pourquoi `randoms` ne retourne pas de nouveau générateur avec la liste ? On -peut implémenter `randoms` très facilement ainsi : +peut implémenter `randoms` très facilement ainsi  > randoms' :: (RandomGen g, Random a) => g -> [a] > randoms' gen = let (value, newGen) = random gen in value:randoms' newGen @@ -1847,7 +1861,7 @@ nouveau générateur en queue. Puisqu'on doit potentiellement générer une list infinie de nombres, on ne peut pas renvoyer le nouveau générateur. On pourrait faire une fonction qui génère un flot fini de nombres aléatoires et -un nouveau générateur ainsi : +un nouveau générateur ainsi  > finiteRandoms :: (RandomGen g, Random a, Num n) => n -> g -> ([a], g) > finiteRandoms 0 gen = ([], gen) @@ -1877,7 +1891,7 @@ pour la valeur produite. > (3,1250031057 40692) Il y a aussi `randomRs`, qui produit un flot de valeurs aléatoires dans -l'intervalle spécifié. Regardez ça : +l'intervalle spécifié. Regardez ça  > ghci> take 10 $ randomRs ('a','z') (mkStdGen 3) :: [Char] > "ndkxbvmomg" @@ -1912,7 +1926,7 @@ Voici un simple programme qui génère une chaîne aléatoire. > bakzhnnuzrkgvesqplrx Attention cependant, faire `getStdGen` deux fois vous donnera le même -générateur global deux fois. Donc si vous faites : +générateur global deux fois. Donc si vous faites  > import System.Random > @@ -2028,7 +2042,7 @@ Voici notre programme en action ! > Sorry, it was 10 > Which number in the range from 1 to 10 am I thinking of? -Une autre manière d'écrire le même programme est comme suit : +Une autre manière d'écrire le même programme est comme suit  > import System.Random > import Control.Monad(when) @@ -2072,21 +2086,22 @@ depuis l'entrée standard ou un fichier. On peut juste ouvrir un fichier, et le lire comme une chaîne de caractères, alors qu'en réalité il ne sera accédé que quand ce sera nécessaire. -Cependant, traiter les fichiers comme des listes a un inconvénient : ça a +Cependant, traiter les fichiers comme des listes a un inconvénient  ça a tendance à être assez lent. Comme vous le savez, `String` est un synonyme de type pour `[Char]`. Les `Char` n'ont pas une taille fixe, parce qu'il faut plusieurs octets pour représenter un caractère, par exemple Unicode. De plus, -les listes sont vraiment paresseuses. Si vous avez une liste comme `[1, 2, 3, -4]`, elle ne sera évaluée que lorsque ce sera vraiment nécessaire. La liste -entière est une promesse de liste. Rappelez-vous que `[1, 2, 3, 4]` est un -sucre syntaxique pour `1:2:3:4:[]`. Lorsque le premier élément de la liste est -forcé à être évalué (par exemple, en l'affichant), le reste de la liste -`2:3:4:[]` est toujours une promesse de liste, et ainsi de suite. Vous pouvez -donc imaginer les listes comme des promesses que l'élément suivant sera délivré -quand on en aura besoin, ainsi que la promesse que la suite fera pareil. Il ne -faut pas se creuser l'esprit bien longtemps pour se dire que traiter une simple -liste de nombres comme une série de promesses n'est peut-être pas la manière la -plus efficace au monde. + les listes sont vraiment paresseuses. Si vous avez une liste comme + `[1, 2, 3, 4]`, elle ne sera évaluée que lorsque ce sera vraiment + nécessaire. La liste entière est une promesse de liste. Rappelez-vous + que `[1, 2, 3, 4]` est un sucre syntaxique pour `1:2:3:4:[]`. Lorsque + le premier élément de la liste est forcé à être évalué (par exemple, + en l'affichant), le reste de la liste `2:3:4:[]` est toujours + une promesse de liste, et ainsi de suite. Vous pouvez donc imaginer + les listes comme des promesses que l'élément suivant sera délivré + quand on en aura besoin, ainsi que la promesse que la suite fera + pareil. Il ne faut pas se creuser l'esprit bien longtemps pour se + dire que traiter une simple liste de nombres comme une série de + promesses n'est peut-être pas la manière la plus efficace au monde. Ce coût supplémentaire ne nous dérange pas la plupart du temps, mais il s'avère handicapant lorsque l'on lit et manipule des gros fichiers. C'est pourquoi @@ -2094,16 +2109,16 @@ Haskell a des **chaînes d'octets**. Les chaînes d'octets sont un peu comme des listes, seulement chaque élément fait un octet (ou 8 bits) de taille. La manière dont elles sont paresseuses est aussi différente. -Les chaînes d'octets viennent sous deux déclinaisons : les strictes et les +Les chaînes d'octets viennent sous deux déclinaisons  les strictes et les paresseuses. Les chaînes d'octets strictes résident dans `Data.ByteString` et elles abandonnent complètement la paresse. Plus de promesses impliquées, une chaîne d'octets stricte est une série d'octets dans un tableau. Vous ne pouvez pas avoir de chaîne d'octets stricte infinie. Si vous évaluez le premier octet d'une chaîne stricte, elle est évaluée en entier. Le bon côté des choses, c'est qu'il y a moins de coût supplémentaire puisqu'il n'y a plus de glaçons (le -terme technique pour les promesses) impliqués. Le mauvais côté, c'est qu'elles -risquent plus de remplir votre mémoire, parce qu'elles sont lues entièrement -dans la mémoire d'un coup. + terme technique pour les promesses) impliqués. Le mauvais côté, c'est +qu'elles risquent plus de remplir votre mémoire, parce qu'elles sont lues +entièrement dans la mémoire d'un coup. L'autre variété de chaîne d'octets réside dans `Data.ByteString.Lazy`. Elles sont paresseuses, mais pas autant que les listes. Comme on l'a dit plus tôt, il @@ -2433,7 +2448,7 @@ handler est la même chose que `catch toTry handler`, qui correspond bien est la fonction qui prend une `IOError` et retourne une action à exécuter en cas d'exception. -Essayons : +Essayons  > $ runhaskell count_lines.hs i_exist.txt > The file has 3 lines! @@ -2447,10 +2462,10 @@ le type d'erreur. Attraper tous les types d'erreur dans un seul gestionnaire est une mauvaise pratique en Haskell tout comme dans la plupart des autres langages. Et si une exception arrivait que l'on ne désirait pas attraper, comme une interruption du programme par l'utilisateur ? C'est pour cela qu'on va -faire comme dans la plupart des autres langages : on va vérifier de quel type -d'exception il s'agit. Si c'est celui qu'on attendait, on la traite. Sinon, on -la lève à nouveau dans la nature. Modifions notre programme pour n'attraper que -les exceptions liées à l'inexistence du fichier. +faire comme dans la plupart des autres langages  on va vérifier de quel +type d'exception il s'agit. Si c'est celui qu'on attendait, on la traite. +Sinon, on la lève à nouveau dans la nature. Modifions notre programme pour +n'attraper que les exceptions liées à l'inexistence du fichier. > import System.Environment > import System.IO @@ -2491,7 +2506,7 @@ hein ? Il y a plusieurs prédicats qui agissent sur des `IOError`, et lorsqu'une garde n'est pas évaluée comme `True`, l'évaluation passe à la prochaine garde. Les -prédicats sur les `IOError` sont : +prédicats sur les `IOError` sont  * `isAlreadyExistsError` * `isDoesNotExistError` @@ -2510,7 +2525,7 @@ qu'il soit préférable d'utiliser des types comme `Either` et `Maybe` pour exprimer des échecs plutôt que de lancer vous-même des exceptions avec `userError`. -Vous pourriez ainsi avoir un gestionnaire de la sorte : +Vous pourriez ainsi avoir un gestionnaire de la sorte  > handler :: IOError -> IO () > handler e @@ -2568,7 +2583,7 @@ Vous n'avez pas à utiliser un unique gestionnaire pour attraper (i.e. `catch`) toutes les exceptions de la partie I/O de votre code. Vous pouvez simplement protéger certaines parties de votre code avec `catch` ou vous pouvez couvrir plusieurs parties avec `catch` et utiliser différents gestionnaires pour -chacune, ainsi : +chacune, ainsi  > main = do toTry `catch` handler1 > thenTryThis `catch` handler2 @@ -2597,13 +2612,19 @@ ou `Right b` lorsqu'elles sont exécutées. diff --git a/et-pour-quelques-monades-de-plus.mkd b/et-pour-quelques-monades-de-plus.mkd index f7f2b7a..2981d45 100644 --- a/et-pour-quelques-monades-de-plus.mkd +++ b/et-pour-quelques-monades-de-plus.mkd @@ -3,13 +3,18 @@
@@ -58,21 +63,21 @@ registre qui reste attaché au résultat. Par exemple, on peut vouloir munir nos valeurs d'une chaîne de caractères décrivant ce qui se passe, probablement pour déboguer notre programme. Considérez une fonction qui prend un nombre de bandits d'un gang et nous dit si -c'est un gros gang ou pas. C'est une fonction très simple : +c'est un gros gang ou pas. C'est une fonction très simple  > isBigGang :: Int -> Bool > isBigGang x = x > 9 Maintenant, et si au lieu de nous répondre seulement `True` ou `False`, on voulait aussi retourner une chaîne de caractères indiquant ce qu'on a fait ? -Eh bien, il suffit de créer une chaîne et la retourner avec le `Bool` : +Eh bien, il suffit de créer une chaîne et la retourner avec le `Bool`  > isBigGang :: Int -> (Bool, String) > isBigGang x = (x > 9, "Compared gang size to 9.") À présent, au lieu de retourner juste un `Bool`, on retourne un tuple dont la première composante est la vraie valeur de retour, et la seconde est la chaîne -accompagnant la valeur. Il y a un contexte additionnel à présent. Testons : +accompagnant la valeur. Il y a un contexte additionnel à présent. Testons  > ghci> isBigGang 3 > (False,"Compared gang size to 9.") @@ -86,9 +91,9 @@ Jusqu'ici, tout va bien. `isBigGang` prend une valeur normale et retourne une valeur dans un contexte. Comme on vient de le voir, lui donner une valeur normale n'est pas un problème. Et si l'on avait déjà une valeur avec un registre attaché, comme `(3, "Smallish gang.")`, et qu'on voulait la donner à -`isBigGang` ? Il semblerait qu'on se retrouve à nouveau face à la question : si -l'on a une fonction qui prend une valeur normale et retourne une valeur dans un -contexte, comment lui passer une valeur dans un contexte ? +`isBigGang` ? Il semblerait qu'on se retrouve à nouveau face à la +question  si l'on a une fonction qui prend une valeur normale et retourne +une valeur dans un contexte, comment lui passer une valeur dans un contexte ? Quand on explorait la monade `Maybe`, on a créé une fonction `applyMaybe`, qui prenait un `Maybe a` et une fonction de type `a -> Maybe b` et on donnait la @@ -106,7 +111,7 @@ String)`, et qui donne cette valeur à cette fonction. On va l'appeler d'échec potentiel, mais plutôt un contexte de valeur additionnelle, `applyLog` va s'assurer que le registre de la valeur originale n'est pas perdu, mais est accolé au registre de la valeur résultant de la fonction. Voici -l'implémentation d'`applyLog` : +l'implémentation d'`applyLog`  > applyLog :: (a,String) -> (a -> (b,String)) -> (b,String) > applyLog (x,log) f = let (y,newLog) = f x in (y,log ++ newLog) @@ -124,7 +129,7 @@ si l'on retournait cela en résultat, on aurait oublié l'ancien registre, ainsi on retourne une paire `(y, log ++ newLog)`. On utilise `++` pour juxtaposer le nouveau registre et l'ancien. -Voici `applyLog` en action : +Voici `applyLog` en action  > ghci> (3, "Smallish gang.") `applyLog` isBigGang > (False,"Smallish gang.Compared gang size to 9") @@ -133,7 +138,7 @@ Voici `applyLog` en action : Les résultats sont similaires aux précédents, seulement le nombre de personne dans le gang avait un registre l'accompagnant, et ce registre a été inclus dans -le registre résultant. Voici d'autres exemples d'utilisation d'`applyLog` : +le registre résultant. Voici d'autres exemples d'utilisation d'`applyLog`  > ghci> ("Tobin","Got outlaw name.") `applyLog` (\x -> (length x, "Applied length.")) > (5,"Got outlaw name.Applied length.") @@ -160,7 +165,7 @@ Pour l'instant, `applyLog` prend des valeurs de type `(a, String)`, mais y a-t-il une raison à ce que le registre soit une `String` ? On utilise `++` pour juxtaposer les registres, ne devrait-ce donc pas marcher pour n'importe quel type de liste, pas seulement des listes de caractères ? Bien sûr que oui. On -peut commencer par changer son type en : +peut commencer par changer son type en  > applyLog :: (a,[c]) -> (a -> (b,[c])) -> (b,[c]) @@ -175,7 +180,7 @@ chaînes d'octets. Mais attendez ! Les listes et les chaînes d'octets sont des monoïdes. En tant que tels, elles sont toutes deux des instances de la classe de types `Monoid`, ce qui signifie qu'elles implémentent la fonction `mappend`. Et pour les listes autant que les chaînes d'octets, `mappend` sert à -concaténer. Regardez : +concaténer. Regardez  > ghci> [1,2,3] `mappend` [4,5,6] > [1,2,3,4,5,6] @@ -184,7 +189,7 @@ concaténer. Regardez : Cool ! Maintenant, `applyLog` peut fonctionner sur n'importe quel monoïde. On doit changer son type pour refléter cela, ainsi que son implémentation pour -remplacer `++` par `mappend` : +remplacer `++` par `mappend`  > applyLog :: (Monoid m) => (a,m) -> (a -> (b,m)) -> (b,m) > applyLog (x,log) f = let (y,newLog) = f x in (y,log `mappend` newLog) @@ -195,7 +200,7 @@ un tuple valeur et valeur monoïdale. Par exemple, on peut avoir un tuple contenant un nom d'objet et un prix en tant que valeur monoïdale. On utilise le *newtype* `Sum` pour s'assurer que les prix sont bien additionnés lorsqu'on opère sur les objets. Voici une fonction qui ajoute des boissons à de la -nourriture de cow-boy : +nourriture de cow-boy  > import Data.Monoid > @@ -210,7 +215,7 @@ nourriture de cow-boy : On utilise des chaînes de caractères pour représenter la nourriture, et un `Int` dans un *newtype* `Sum` pour tracer le nombre de centimes que quelque chose coûte. Juste un rappel, faire `mappend` sur des `Sum` résulte en la somme -des valeurs enveloppées : +des valeurs enveloppées  > ghci> Sum 3 `mappend` Sum 9 > Sum {getSum = 12} @@ -218,9 +223,9 @@ des valeurs enveloppées : La fonction `addDrink` est plutôt simple. Si l'on mange des haricots, elle retourne `"milk"` ainsi que `Sum 25`, donc 25 centimes encapsulés dans un `Sum`. Si l'on mange du bœuf séché, on boit du whisky, et si l'on mange quoi -que ce soit d'autre, on boit une bière. Appliquer normalement une fonction à -de la nourriture ne serait pas très intéressant ici, mais utiliser `applyLog` -pour donner une nourriture qui a un prix à cette fonction est intéressant : +que ce soit d'autre, on boit une bière. Appliquer normalement une fonction à de +la nourriture ne serait pas très intéressant ici, mais utiliser `applyLog` pour +donner une nourriture qui a un prix à cette fonction est intéressant  > ghci> ("beans", Sum 10) `applyLog` addDrink > ("milk",Sum {getSum = 35}) @@ -238,7 +243,8 @@ présent, les nombres sont sommés. Puisque la valeur qu'`addDrink` retourne est un tuple `(Food, Price)`, on peut donner ce résultat à `addDrink` à nouveau, pour qu'elle nous dise ce qu'on -devrait boire avec notre boisson et combien le tout nous coûterait. Essayons : +devrait boire avec notre boisson et combien le tout nous coûterait. +Essayons  > ghci> ("dogmeat", Sum 5) `applyLog` addDrink `applyLog` addDrink > ("beer",Sum {getSum = 65}) @@ -257,7 +263,7 @@ valeur monadique, examinons l'instance de `Monad` pour de tels types. Le module D'abord, examinons le type lui-même. Pour attacher un monoïde à une valeur, on doit simplement les placer ensemble dans un tuple. Le type `Writer w a` est -juste un enrobage *newtype* de cela. Sa définition est très simple : +juste un enrobage *newtype* de cela. Sa définition est très simple  > newtype Writer w a = Writer { runWriter :: (a, w) } @@ -266,7 +272,7 @@ séparer ce type des tuples ordinaires. Le paramètre de type `a` représente le type de la valeur, alors que le paramètre de type `w` est la valeur monoïdale attachée. -Son instance de `Monad` est définie de la sorte : +Son instance de `Monad` est définie de la sorte  > instance (Monoid w) => Monad (Writer w) where > return x = Writer (x, mempty) @@ -296,7 +302,7 @@ valeur. Ainsi, si l'on utilise `return` pour créer une valeur `Writer` et qu'on utilise `>>=` pour donner cette valeur à une fonction, la valeur monoïdale résultante sera uniquement ce que la fonction retourne. Utilisons `return` sur le nombre `3` quelques fois, en lui attachant un monoïde différent à chaque -fois : +fois  > ghci> runWriter (return 3 :: Writer String Int) > (3,"") @@ -343,7 +349,7 @@ singleton qui dit simplement qu'on a ce nombre. `multWithLog` est une valeur inclus dans le registre final. On utilise `return` pour présenter `a*b` comme résultat. Puisque `return` prend simplement quelque chose et le place dans un contexte minimal, on peut être sûr de ne rien avoir ajouté au registre. Voici -ce qu'on voit en évaluant ceci : +ce qu'on voit en évaluant ceci  > ghci> runWriter multWithLog > (15,["Got number: 3","Got number: 5"]) @@ -355,7 +361,7 @@ monoïdale, comme `["This is going on"]` et crée une valeur `Writer` qui présente la valeur factice `()` comme son résultat, mais avec notre valeur monoïdale attachée. Quand on a une valeur monoïdale qui a un `()` en résultat, on ne le lie pas à une variable. Voici `multWithLog` avec un message -supplémentaire rapporté dans le registre : +supplémentaire rapporté dans le registre  > multWithLog :: Writer [String] Int > multWithLog = do @@ -368,7 +374,7 @@ Il est important que `return (a*b)` soit la dernière ligne, parce que le résultat de la dernière ligne d'une expression `do` est le résultat de l'expression entière. Si l'on avait placé `tell` à la dernière ligne, `()` serait le résultat de l'expression `do`. On aurait perdu le résultat de la -multiplication. Cependant, le registre serait le même. Place à l'action : +multiplication. Cependant, le registre serait le même. Place à l'action  > ghci> runWriter multWithLog > (15,["Got number: 3","Got number: 5","Gonna multiply these two"]) @@ -379,7 +385,7 @@ L'algorithme d'Euclide est un algorithme qui prend deux nombres et calcule leur plus grand commun diviseur. C'est-à-dire, le plus grand nombre qui divise à la fois ces deux nombres. Haskell contient déjà la fonction `gcd`, qui calcule exactement ceci, mais implémentons la nôtre avec des capacités de registre. -Voici l'algorithme normal : +Voici l'algorithme normal  > gcd' :: Int -> Int -> Int > gcd' a b @@ -397,7 +403,7 @@ diviseur de 3 et 2. 2 est différent de 0, on obtient donc 2 et 1. Le second nombre n'est toujours pas 0, alors on lance l'algorithme à nouveau sur 1 et 0, puisque diviser 2 par 1 nous donne un reste de 0. Finalement, puisque le second nombre est 0, alors le premier est le résultat final, c'est-à-dire 1. Voyons si -le code est d'accord : +le code est d'accord  > ghci> gcd' 8 3 > 1 @@ -405,12 +411,12 @@ le code est d'accord : C'est le cas. Très bien ! Maintenant, on veut munir notre résultat d'un contexte, et ce contexte sera une valeur monoïdale agissant comme un registre. Comme auparavant, on utilisera une liste de chaînes de caractères pour notre -monoïde. Le type de notre nouvelle fonction `gcd'` devrait donc être : +monoïde. Le type de notre nouvelle fonction `gcd'` devrait donc être  > gcd' :: Int -> Int -> Writer [String] Int Il ne reste plus qu'à munir notre fonction de valeurs avec registres. Voici le -code : +code  > import Control.Monad.Writer > @@ -429,7 +435,7 @@ vaut `0`, plutôt que de donner simplement le `a` en résultat, on utilise une expression `do` pour le placer dans une valeur `Writer` en résultat. On utilise d'abord `tell` pour rapporter qu'on a terminé, puis `return` pour présenter `a` comme résultat de l'expression `do`. Au lieu de cette expression `do`, on -aurait aussi pu écrire : +aurait aussi pu écrire  > Writer (a, ["Finished with " ++ show a]) @@ -449,13 +455,15 @@ devrait être. Essayons notre nouvelle fonction `gcd'`. Son résultat est une valeur `Writer [String] Int` et si on l'extrait de son *newtype*, on obtient un tuple. La -première composante de cette paire est le résultat. Voyons si c'est le cas : +première composante de cette paire est le résultat. Voyons si c'est le +cas  > ghci> fst $ runWriter (gcd' 8 3) > 1 Bien ! Qu'en est-il du registre ? Puisqu'il n'est qu'une liste de chaînes de -caractères, utilisons `mapM_ putStrLn` pour afficher ces chaînes à l'écran : +caractères, utilisons `mapM_ putStrLn` pour afficher ces chaînes à +l'écran  > ghci> mapM_ putStrLn $ snd $ runWriter (gcd' 8 3) > 8 mod 3 = 2 @@ -481,7 +489,7 @@ lent. C'est parce que les listes utilisent `++` pour `mappend`, et utiliser la liste est très longue. Dans notre fonction `gcd'`, la construction du registre est rapide parce que la -concaténation se déroule ainsi : +concaténation se déroule ainsi  > a ++ (b ++ (c ++ (d ++ (e ++ f)))) @@ -489,7 +497,7 @@ Les listes sont des structures de données construites de la gauche vers la droite, et ceci est efficace parce que l'on construit d'abord entièrement la partie de gauche d'une liste, et ensuite on ajoute une liste plus longue à droite. Mais si l'on ne fait pas attention, utiliser la monade `Writer` peut -produire une concaténation comme celle-ci : +produire une concaténation comme celle-ci  > ((((a ++ b) ++ c) ++ d) ++ e) ++ f @@ -517,7 +525,7 @@ Elle effectue l'appel récursif d'abord, et lie le résultat au nom `result`. Puis, elle ajoute l'étape courante au registre, mais celle-ci vient donc s'ajouter à la fin du registre produit par l'appel récursif. Finalement, elle présente le résultat de l'appel récursif comme résultat final. La voici en -action : +action  > ghci> mapM_ putStrLn $ snd $ runWriter (gcdReverse 8 3) > Finished with 1 @@ -546,7 +554,7 @@ concaténation efficace. Quand on concatène deux listes normales avec `++`, ell doit traverser la liste de la gauche jusqu'à sa fin pour coller la liste de droite à cet endroit. Mais qu'en est-il avec l'approche des listes différentielles ? Eh bien, concaténer deux listes différentielles peut être -fait ainsi : +fait ainsi  > f `append` g = \xs -> f (g xs) @@ -554,7 +562,7 @@ Souvenez-vous, `f` et `g` sont des fonctions qui prennent une liste, et leur prépose quelque chose. Par exemple, si `f` est la fonction `("dog"++)` (qui est juste une autre façon d'écrire `\xs -> "dog" ++ xs`) et `g` est la fonction `("meat"++)`, alors f \`append\` g crée une nouvelle fonction -équivalente à : +équivalente à  > \xs -> "dog" ++ ("meat" ++ xs) @@ -563,14 +571,14 @@ nouvelle fonction qui applique d'abord une liste différentielle sur une liste, puis applique l'autre liste différentielle au résultat. Créons un emballage *newtype* pour nos listes différentielles de façon à -pouvoir les doter d'une instance de monoïde : +pouvoir les doter d'une instance de monoïde  > newtype DiffList a = DiffList { getDiffList :: [a] -> [a] } Le type que l'on enveloppe est `[a] -> [a]` parce qu'une liste différentielle est simplement une fonction qui prend une liste et en retourne une autre. Convertir des listes normales en listes différentielles et vice versa est très -facile : +facile  > toDiffList :: [a] -> DiffList a > toDiffList xs = DiffList (xs++) @@ -584,20 +592,22 @@ Puisqu'une liste différentielle est une fonction qui prépose une autre liste la liste qu'elle représente, pour obtenir cette liste, il suffit de l'appliquer à une liste vide ! -Voici l'instance de `Monoid` : +Voici l'instance de `Monoid`  > instance Monoid (DiffList a) where > mempty = DiffList (\xs -> [] ++ xs) > (DiffList f) `mappend` (DiffList g) = DiffList (\xs -> f (g xs)) Remarquez comme pour ces listes, `mempty` est juste la fonction `id` et -`mappend` est simplement la composition de fonctions. Voyons si cela marche : +`mappend` est simplement la composition de fonctions. Voyons si cela +marche  > ghci> fromDiffList (toDiffList [1,2,3,4] `mappend` toDiffList [1,2,3]) > [1,2,3,4,1,2,3] Tip top ! On peut maintenant améliorer l'efficacité de `gcdReverse` en lui -faisant utiliser des listes différentielles plutôt que des listes normales : +faisant utiliser des listes différentielles plutôt que des listes +normales  > import Control.Monad.Writer > @@ -614,7 +624,7 @@ faisant utiliser des listes différentielles plutôt que des listes normales : On a simplement dû changer le type du monoïde de `[String]` en `DiffList String`, et changer nos listes normales en listes différentielles avec `toDiffList` quand on utilisait `tell`. Voyons si le registre est assemblé -correctement : +correctement  > ghci> mapM_ putStrLn . fromDiffList . snd . runWriter $ gcdReverse 110 34 > Finished with 2 @@ -633,7 +643,7 @@ 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 : +nombres dans l'ordre croissant  > finalCountDown :: Int -> Writer (DiffList String) () > finalCountDown 0 = do @@ -650,7 +660,7 @@ 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 : +l'avant  > ghci> mapM_ putStrLn . fromDiffList . snd . runWriter $ finalCountDown 500000 > 0 @@ -659,7 +669,7 @@ l'avant : > ... Cependant, si on la change pour utiliser des listes normales plutôt que des -listes différentielles, de cette manière : +listes différentielles, de cette manière  > finalCountDown :: Int -> Writer [String] () > finalCountDown 0 = do @@ -668,7 +678,7 @@ listes différentielles, de cette manière : > finalCountDown (x-1) > tell [show x] -Et qu'on demande à GHCi de compter : +Et qu'on demande à GHCi de compter  > ghci> mapM_ putStrLn . snd . runWriter $ finalCountDown 500000 @@ -694,7 +704,7 @@ 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 : +son résultat. Par exemple  > ghci> let f = (*5) > ghci> let g = (+3) @@ -703,7 +713,7 @@ son résultat. Par exemple : 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 : +avait déjà ces résultats. Par exemple  > ghci> let f = (+) <$> (*2) <*> (+10) > ghci> f 3 @@ -725,7 +735,7 @@ 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 : +ressemble à ça  > instance Monad ((->) r) where > return x = \_ -> x @@ -751,7 +761,7 @@ 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 : +une expression `do` qui utilise cette monade  > import Control.Monad.Instances > @@ -770,7 +780,7 @@ sur ce nombre, ce qui résulte en `a`. `(+10)` est également appliquée au mêm 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 : +cette fonction. Si on essaie, on obtient le même résultat qu'avant  > ghci> addStuff 3 > 19 @@ -780,7 +790,7 @@ cette fonction. Si on essaie, on obtient le même résultat qu'avant : 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 : +ainsi  > addStuff :: Int -> Int > addStuff x = let @@ -822,7 +832,7 @@ aléatoire et un nouveau générateur aléatoire. Si l'on voulait générer plus nombres aléatoires, nous avions toujours un générateur obtenu en résultat en même temps que le nombre aléatoire précédent. Quand on avait écrit une fonction prenant un `StdGen` et jetant trois fois une pièce en se basant sur un -générateur, on avait fait : +générateur, on avait fait  > threeCoins :: StdGen -> (Bool, Bool, Bool) > threeCoins gen = @@ -849,7 +859,7 @@ cool. Donc, pour nous aider à mieux comprendre le concept de calculs à états, donnons leur un type. On dira qu'un calcul à états est une fonction qui prend un état -et retourne une valeur et un nouvel état. Le type de la fonction serait : +et retourne une valeur et un nouvel état. Le type de la fonction serait  > s -> (a,s) @@ -885,10 +895,10 @@ en bas de la pile, il faut d'abord dépiler tout ce qui est au dessus. Nous utiliserons une liste pour notre pile et la tête de la liste sera le sommet de la pile. Pour nous aider dans notre tâche, nous créerons deux -fonctions : `pop` et `push`. `pop` prend une pile, dépile un élément, et +fonctions  `pop` et `push`. `pop` prend une pile, dépile un élément, et retourne l'élément en résultat ainsi que la nouvelle pile sans cet élément. `push` prend un élément et une pile et empile l'élément sur la pile. Elle -retourne `()` en résultat, ainsi qu'une nouvelle pile. Voici : +retourne `()` en résultat, ainsi qu'une nouvelle pile. Voici  > type Stack = [Int] > @@ -906,7 +916,7 @@ son type. Écrivons un petit bout de code qui simule une pile en utilisant ces fonctions. On va prendre une pile, empiler `3` et dépiler deux éléments, juste pour voir. -Voici : +Voici  > stackManip :: Stack -> (Int, Stack) > stackManip stack = let @@ -920,7 +930,7 @@ pile, qu'on appelle `newStack1`. Ensuite, on dépile un nombre de `newStack1`, ce qui retourne un nombre `a` (qui est `3`) et une nouvelle pile qu'on appelle `newStack2`. Puis, on dépile un nombre de `newStack2` et obtient un nombre `b` et une nouvelle pile `newStack3`. Le tuple de ce nombre et cette pile est -retourné. Essayons : +retourné. Essayons  > ghci> stackManip [5,8,2,1] > (5,[8,2,1]) @@ -932,7 +942,7 @@ Cool, le résultat est `5` et la nouvelle pile est `[8, 2, 1]`. Remarquez comme Le code ci-dessus de `stackManip` est un peut fastidieux puisqu'on donne manuellement l'état à chaque calcul à états, puis on récupère un nouvel état qu'on donne à nouveau au prochain calcul. Ce serait mieux si, au lieu de donner -les piles manuellement à chaque fonction, on pouvait écrire : +les piles manuellement à chaque fonction, on pouvait écrire  > stackManip = do > push 3 @@ -946,7 +956,7 @@ préoccuper de la gestion de l'état manuellement.

La monade State

Le module `Control.Monad.State` fournit un *newtype* qui enveloppe des calculs -à états. Voici sa définition : +à états. Voici sa définition  > newtype State s a = State { runState :: s -> (a,s) } @@ -955,7 +965,7 @@ retourne un résultat de type `a`. Maintenant qu'on a vu ce que sont des calculs à états et comment ils pouvaient être vus comme des valeurs avec des contextes, regardons leur instance de -`Monad` : +`Monad`  > instance Monad (State s) where > return x = State $ \s -> (x,s) @@ -979,20 +989,21 @@ commence donc à écrire le *newtype* `State` et une lambda. Cette lambda doit extraire le résultat du premier calcul à états d'une manière ou d'une autre. Puisqu'on se trouve dans un calcul à états, on peut donner au calcul à états `h` notre état actuel `s`, ce qui retourne une paire d'un résultat et d'un -nouvel état : `(a, newState)`. À chaque fois qu'on a implémenté `>>=`, après -avoir extrait le résultat de la valeur monadique, on appliquait la fonction `f` -dessus pour obtenir une nouvelle valeur monadique. Dans `Writer`, après avoir -fait cela et obtenu la nouvelle valeur monadique, on devait ne pas oublier de -tenir compte du contexte en faisant `mappend` entre l'ancienne valeur monoïdale -et la nouvelle. Ici, on fait `f a` et on obtient un nouveau calcul à états `g`. -Maintenant qu'on a un calcul à états et un état (qui s'appelle `newState`) on -applique simplement le calcul à états `g` à l'état `newState`. Le résultat est -un tuple contenant le résultat final et l'état final ! +nouvel état  `(a, newState)`. À chaque fois qu'on a implémenté `>>=`, +après avoir extrait le résultat de la valeur monadique, on appliquait la +fonction `f` dessus pour obtenir une nouvelle valeur monadique. Dans `Writer`, +après avoir fait cela et obtenu la nouvelle valeur monadique, on devait ne pas +oublier de tenir compte du contexte en faisant `mappend` entre l'ancienne +valeur monoïdale et la nouvelle. Ici, on fait `f a` et on obtient un nouveau +calcul à états `g`. Maintenant qu'on a un calcul à états et un état (qui +s'appelle `newState`) on applique simplement le calcul à états `g` à l'état +`newState`. Le résultat est un tuple contenant le résultat final et l'état +final ! Ainsi, avec `>>=`, on colle ensemble deux calculs à états, seulement le second est caché dans une fonction qui prend le résultat du premier. Puisque `pop` et `push` sont déjà des calculs à états, il est facile de les envelopper dans un -`State`. Regardez : +`State`. Regardez  > import Control.Monad.State > @@ -1004,7 +1015,7 @@ est caché dans une fonction qui prend le résultat du premier. Puisque `pop` et `pop` est déjà un calcul à états et `push` prend un `Int` et retourne un calcul à états. Maintenant, on peut réécrire l'exemple précédent où l'on empilait `3` -sur la pile avant de dépiler deux nombres ainsi : +sur la pile avant de dépiler deux nombres ainsi  > import Control.Monad.State > @@ -1016,13 +1027,13 @@ sur la pile avant de dépiler deux nombres ainsi : Voyez-vous comme on a collé ensemble un empilement et deux dépilements en un calcul à états ? Quand on sort ce calcul de son *newtype*, on obtient une -fonction à laquelle on peut fournir un état initial : +fonction à laquelle on peut fournir un état initial  > ghci> runState stackManip [5,8,2,1] > (5,[8,2,1]) On n'avait pas eu besoin de lier le premier `pop` à `a` vu qu'on n'utilise pas -ce `a`. On aurait pu écrire : +ce `a`. On aurait pu écrire  > stackManip :: State Stack Int > stackManip = do @@ -1030,9 +1041,9 @@ ce `a`. On aurait pu écrire : > pop > pop -Plutôt cool. Mais et si l'on voulait faire ceci : dépiler un nombre de la pile, -puis si ce nombre est `5`, l'empiler à nouveau et sinon, empiler `3` et `8` -plutôt ? Voici le code : +Plutôt cool. Mais et si l'on voulait faire ceci  dépiler un nombre de la +pile, puis si ce nombre est `5`, l'empiler à nouveau et sinon, empiler `3` et +`8` plutôt ? Voici le code  > stackStuff :: State Stack () > stackStuff = do @@ -1066,18 +1077,18 @@ et ne fait rien. Le module `Control.Monad.State` fournit une classe de types appelée `MonadState` qui contient deux fonctions assez utiles, j'ai nommé `get` et -`put`. Pour `State`, la fonction `get` est implémentée ainsi : +`put`. Pour `State`, la fonction `get` est implémentée ainsi  > get = State $ \s -> (s,s) Elle prend simplement l'état courant et le présente en résultat. La fonction `put` prend un état et crée une fonction à états qui remplace l'état courant -par celui-ci : +par celui-ci  > put newState = State $ \s -> ((),newState) Avec ces deux fonctions, on peut voir la pile courante ou la remplacer par une -toute nouvelle pile. Comme ça : +toute nouvelle pile. Comme ça  > stackyStack :: State Stack () > stackyStack = do @@ -1087,7 +1098,7 @@ toute nouvelle pile. Comme ça : > else put [9,2,1] Il est intéressant d'examiner le type qu'aurait `>>=` si elle était restreinte -aux valeurs `State` : +aux valeurs `State`  > (>>=) :: State s a -> (a -> State s b) -> State s b @@ -1095,7 +1106,7 @@ Remarquez que le type de l'état `s` reste le même, mais le type du résultat peut changer de `a` en `b`. Cela signifie que l'on peut coller ensemble des calculs à états dont les résultats sont de différents types, mais le type des états doit être le même. Pourquoi cela ? Eh bien, par exemple, pour `Maybe`, -`>>=` a ce type : +`>>=` a ce type  > (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b @@ -1112,14 +1123,14 @@ et retourne un nombre aléatoire ainsi qu'un nouveau générateur, qui devait ensuite être utilisé à la place du précédent pour générer de nouveaux nombres aléatoires. La monade d'états rend cela bien plus facile. -La fonction `random` de `System.Random` a pour type : +La fonction `random` de `System.Random` a pour type  > random :: (RandomGen g, Random a) => g -> (a, g) Signifiant qu'elle prend un générateur aléatoire et produit un nombre aléatoire et un nouveau générateur. On peut voir que c'est un calcul à états, on peut -donc l'envelopper dans le constructeur de *newtype* `State` et l'utiliser -comme une valeur monadique afin que la gestion de l'état soit fait pour nous : +donc l'envelopper dans le constructeur de *newtype* `State` et l'utiliser comme +une valeur monadique afin que la gestion de l'état soit fait pour nous  > import System.Random > import Control.Monad.State @@ -1128,7 +1139,7 @@ comme une valeur monadique afin que la gestion de l'état soit fait pour nous : > randomSt = State random À présent, si l'on souhaite lancer trois pièces (`True` pour pile, `False` pour -face), on peut faire : +face), on peut faire  > import System.Random > import Control.Monad.State @@ -1144,7 +1155,7 @@ face), on peut faire : initial, il le passe au premier `randomSt`, qui produit un nombre et un nouveau générateur, qui est passé au prochain et ainsi de suite. On utilise `return (a, b, c)` pour présenter `(a, b, c)` comme le résultat sans changer le générateur -le plus récent. Essayons : +le plus récent. Essayons  > ghci> runState threeCoins (mkStdGen 33) > ((True,False,True),680029187 2103410263) @@ -1164,7 +1175,7 @@ Le type `Either e a` au contraire, nous permet d'incorporer un contexte d'échec afin de décrire ce qui s'est mal passé et de fournir d'autres informations intéressantes concernant l'échec. Une valeur `Either e a` peut être ou bien une valeur `Right`, indiquant la bonne réponse et un succès, ou une valeur `Left`, -indiquant l'échec. Par exemple : +indiquant l'échec. Par exemple  > ghci> :t Right 4 > Right 4 :: (Num t) => Either a t @@ -1177,7 +1188,7 @@ d'échec éventuel, seulement maintenant il y a une valeur attachée à cette erreur. Son instance de `Monad` est similaire à celle de `Maybe` et peut être trouvée -dans `Control.Monad.Error` : +dans `Control.Monad.Error`  > instance (Error e) => Monad (Either e) where > return x = Right x @@ -1190,10 +1201,10 @@ défaut minimal. Elle enveloppe notre valeur dans le constructeur `Right` parce qu'on utilise `Right` pour représenter un calcul réussi pour lequel un résultat est présent. C'est presque comme le `return` de `Maybe`. -`>>=` examine deux cas possibles : un `Left` ou un `Right`. Dans le cas d'un -`Right`, la fonction `f` est appliquée à la valeur à l'intérieur, comme pour -`Just`. Dans le cas d'une erreur, la valeur `Left` est conservée ainsi que son -contenu qui décrit l'erreur. +`>>=` examine deux cas possibles  un `Left` ou un `Right`. Dans le cas +d'un `Right`, la fonction `f` est appliquée à la valeur à l'intérieur, comme +pour `Just`. Dans le cas d'une erreur, la valeur `Left` est conservée ainsi que +son contenu qui décrit l'erreur. L'instance de `Monad` d'`Either e a` a un pré-requis additionnel, qui est que le type de la valeur contenur dans `Left`, celui indexé par le paramètre de @@ -1202,7 +1213,7 @@ types `Error` est pour les types dont les valeurs peuvent être vues comme des messages d'erreur. Elle définit une fonction `strMsg`, qui prend une erreur sous la forme d'une chaîne de caractères et retourne une telle valeur. Un bon exemple d'instance d'`Error` est, eh bien, le type `String` ! Dans le cas de -`String`, la fonction `strMsg` retourne simplement ce qu'elle a reçu : +`String`, la fonction `strMsg` retourne simplement ce qu'elle a reçu  > ghci> :t strMsg > strMsg :: (Error a) => String -> a @@ -1214,7 +1225,7 @@ utilise `Either`, on n'a pas trop à s'en soucier. Quand un filtrage par motif échoue dans la notation *do*, une valeur `Left` est utilisée pour indiquer cet échec. -Voici quelques exemples d'utilisation : +Voici quelques exemples d'utilisation  > ghci> Left "boom" >>= \x -> return (x+1) > Left "boom" @@ -1242,7 +1253,7 @@ Haskell dit qu'il ne sait pas quel type choisir pour la partie `e` de notre valeur `Either e a`, bien qu'on n'ait seulement affiché la partie `Right`. Ceci est dû à la contrainte `Error e` de l'instance de `Monad`. Ainsi, si vous obtenez une telle erreur de type en utilisant la monade `Either`, ajoutez une -signature de type explicite : +signature de type explicite  > ghci> Right 3 >>= \x -> return (x + 100) :: Either String Int > Right 103 @@ -1290,12 +1301,12 @@ foncteur applicatif, elle ne l'a pas, parce que la classe de types `Monad` a 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` : +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` : +Et le type de `fmap`  > fmap :: (Functor f) => (a -> b) -> f a -> f b @@ -1304,7 +1315,7 @@ 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` : +`liftM`  > ghci> liftM (*3) (Just 8) > Just 24 @@ -1326,12 +1337,12 @@ composante du tuple, qui est le résultat. Daire `fmap` ou `liftM` sur un calcul modifié par la fonction passée en argument. Si l'on avait pas mappé `(+100)` sur `pop`, elle aurait retourné `(1,[2,3,4])`. -Voici comment `liftM` est implémentée : +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* : +Ou, en notation *do*  > liftM :: (Monad m) => (a -> b) -> m a -> m b > liftM f m = do @@ -1349,7 +1360,7 @@ 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 : +Comme cela  > ghci> (+) <$> Just 3 <*> Just 5 > Just 8 @@ -1358,7 +1369,7 @@ Comme cela : 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 : +type  > (<*>) :: (Applicative f) => f (a -> b) -> f a -> f b @@ -1372,7 +1383,7 @@ 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 : +qu'`Applicative`. Voici sa définition  > ap :: (Monad m) => m (a -> b) -> m a -> m b > ap mf m = do @@ -1384,7 +1395,7 @@ qu'`Applicative`. Voici sa définition : 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 : +Voici un exemple rapide  > ghci> Just (+3) <*> Just 4 > Just 7 @@ -1404,7 +1415,7 @@ 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 : +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 @@ -1421,19 +1432,19 @@ applicatifs.

La fonction `join`

-Voici de quoi faire travailler vos neurones : si le résultat d'une valeur +Voici de quoi faire travailler vos neurones  si le résultat d'une valeur monadique est une autre valeur monadique, autrement dit si une valeur monadique est imbriquée dans une autre, peut-on les aplatir en une valeur monadique simple ? Par exemple, si l'on a `Just (Just 9)`, peut on en faire un `Just 9` ? Il s'avère que toute valeur monadique imbriquée peut être aplatie et ceci est une propriété unique des monades. Pour cela, la fonction `join` existe. Son -type est : +type est  > join :: (Monad m) => m (m a) -> m a Ainsi, elle prend une valeur monadique dans une valeur monadique et retourne simplement une valeur monadique, donc en quelque sorte elle l'aplatit. La voici -en action sur diverses valeurs `Maybe` : +en action sur diverses valeurs `Maybe`  > ghci> join (Just (Just 9)) > Just 9 @@ -1451,7 +1462,7 @@ soit `Just`. S'il y avait un seul échec parmi elles, le résultat était un échec. Il en va de même ici. À la troisième ligne, on essaie d'aplatir ce qui est déjà un échec, le résultat reste un échec. -Aplatir les listes est intuitif : +Aplatir les listes est intuitif  > ghci> join [[1,2,3],[4,5,6]] > [1,2,3,4,5,6] @@ -1468,7 +1479,7 @@ juxtaposée. Intuitivement, pour examiner la valeur d'un `Writer`, il faut que sa valeur monoïdale soit mise au registre d'abord, et seulement ensuite peut-on examiner ce qu'elle contient. -Aplatir des valeurs `Either` est similaire au cas des valeurs `Maybe` : +Aplatir des valeurs `Either` est similaire au cas des valeurs `Maybe`  > ghci> join (Right (Right 9)) :: Either String Int > Right 9 @@ -1479,7 +1490,7 @@ Aplatir des valeurs `Either` est similaire au cas des valeurs `Maybe` : Si on applique `join` à un calcul à états dont le résultat est un calcul à états, le résultat est un calcul à états qui lance d'abord le calcul extérieur -et ensuite le calcul intérieur. Regardez : +et ensuite le calcul intérieur. Regardez  > ghci> runState (join (State $ \s -> (push 10,1:2:s))) [0,0,0] > ((),[10,1,2,0,0,0]) @@ -1489,7 +1500,7 @@ La lambda ici prend un état et place `2` et `1` dans la pile et présente `push elle empile d'abord `2` et `1` puis `push 10` est exécuté, empilant un `10` en sommet de pile. -L'implémentation de `join` est comme suit : +L'implémentation de `join` est comme suit  > join :: (Monad m) => m (m a) -> m a > join mm = do @@ -1502,7 +1513,7 @@ ici est que lorsqu'on fait `m <- mm`, le contexte de la monade en question est pris en compte. C'est pourquoi, par exemple, des valeurs `Maybe` résultent en des `Just` seulement si les valeurs intérieures et extérieures sont toutes deux des valeurs `Just`. Voici à quoi cela ressemblait si `mm` était fixé à l'avance -à la valeur `Just (Just 8)` : +à la valeur `Just (Just 8)`  > joinedMaybes :: Maybe Int > joinedMaybes = do @@ -1533,7 +1544,7 @@ imbriquée plutôt que de trouver comment implémenter `>>=`. La fonction `filter` est le pain quotidien de la programmation en Haskell (`map` étant le beurre sur la tartine). Elle prend un prédicat et une liste à filtrer et retourne une nouvelle liste dans laquelle tous les éléments -satisfaisant le précidat ont été gardés. Son type est : +satisfaisant le précidat ont été gardés. Son type est  > filter :: (a -> Bool) -> [a] -> [a] @@ -1550,7 +1561,7 @@ résultante ait également un contexte attaché, autrement, le contexte de chaqu `Bool` serait perdu. La fonction `filterM` de `Control.Monad` fait exactement ce que l'on souhaite ! -Son type est : +Son type est  > filterM :: (Monad m) => (a -> m Bool) -> [a] -> m [a] @@ -1561,14 +1572,14 @@ contexte est reflété dans le résultat final, celui-ci est aussi une valeur monadique. Prenons une liste et gardons seulement les valeurs inférieures à 4. Pour -commencer, on va utiliser la fonction `filter` ordinaire : +commencer, on va utiliser la fonction `filter` ordinaire  > ghci> filter (\x -> x < 4) [9,1,5,2,10,3] > [1,2,3] C'était plutôt simple. Maintenant, créons un prédicat qui, en plus de retourner `True` ou `False`, fournisse également un registre de ce qu'il a fait. Bien -sûr, on va utiliser la monade `Writer` à cet effet : +sûr, on va utiliser la monade `Writer` à cet effet  > keepSmall :: Int -> Writer [String] Bool > keepSmall x @@ -1608,7 +1619,7 @@ Une astuce Haskell très cool est d'utiliser `filterM` pour obtenir l'ensemble des parties d'une liste (en imaginant la liste comme un ensemble pour le moment). L'ensemble des parties d'un ensemble est l'ensemble des sous-ensembles de cet ensemble. Si l'on a un ensemble comme `[1, 2, 3]`, l'ensemble de ses -parties contient les ensembles suivants : +parties contient les ensembles suivants  > [1,2,3] > [1,2] @@ -1626,10 +1637,11 @@ cet ensemble. `[2, 3]` est comme l'ensemble original, mais on a exclu le nombre Pour créer une fonction renvoyant l'ensemble des parties d'une liste, on va s'appuyer sur le non déterminisme. On prend une liste `[1, 2, 3]` et on regarde -son premier élément, qui est `1`, et on se demande : devrait-on le garder ou le -jeter ? Eh bien, on aimerait faire les deux en réalité. On va donc filtrer une -liste à l'aide d'un prédicat qui gardera et jettera chaque élément de façon non -déterministe. Voici notre fonction des sous-parties d'un ensemble : +son premier élément, qui est `1`, et on se demande  devrait-on le garder +ou le jeter ? Eh bien, on aimerait faire les deux en réalité. On va donc +filtrer une liste à l'aide d'un prédicat qui gardera et jettera chaque élément +de façon non déterministe. Voici notre fonction des sous-parties d'un +ensemble  > powerset :: [a] -> [[a]] > powerset xs = filterM (\x -> [True, False]) xs @@ -1637,7 +1649,7 @@ déterministe. Voici notre fonction des sous-parties d'un ensemble : Quoi, c'est tout ? Yup. On choisit de jeter et de garder chaque élément, peu importe lequel c'est. Nous avons un prédicat non déterministe, donc la liste résultante sera aussi une valeur non déterministe, et donc une liste de listes. -Essayons : +Essayons  > ghci> powerset [1,2,3] > [[1,2,3],[1,2],[1,3],[1],[2,3],[2],[3],[]] @@ -1656,17 +1668,17 @@ plis](fonctions-d-ordre-superieur#plie-mais-ne-rompt-pas), vous savez que plier, et plie la liste en partant de la gauche avec la fonctions binaire pour n'en faire plus qu'une valeur. `foldM` fait la même chose, mais prend une fonction qui produit une valeur monadique pour plier la liste. Sans surprise, -la valeur résultante est également monadique. Le type de `foldl` est : +la valeur résultante est également monadique. Le type de `foldl` est  > foldl :: (a -> b -> a) -> a -> [b] -> a -Alors que `foldM` a pour type : +Alors que `foldM` a pour type  > foldM :: (Monad m) => (a -> b -> m a) -> a -> [b] -> m a La valeur que la fonction binaire retourne est monadique, et donc le résultat du pli entier est aussi monadique. Sommons une liste de nombres à l'aide d'un -pli : +pli  > ghci> foldl (\acc x -> acc + x) 0 [2,8,3,1] > 14 @@ -1682,7 +1694,7 @@ semble logique d'utiliser une fonction binaire qui vérifie si le nombre à ajouter est plus grand que `9`, échoue le cas échéant, et continue son petit bonhomme de chemin si ce n'est pas le cas. À cause de cette possibilité d'échec additionnelle, faisons retourner à notre fonction un `Maybe` accumulateur -plutôt qu'un accumulateur normal. Voici la fonction binaire : +plutôt qu'un accumulateur normal. Voici la fonction binaire  > binSmalls :: Int -> Int -> Maybe Int > binSmalls acc x @@ -1690,7 +1702,8 @@ plutôt qu'un accumulateur normal. Voici la fonction binaire : > | otherwise = Just (acc + x) Puisque notre fonction binaire est maintenant une fonction monadique, on ne -peut plus utiliser le `foldl` normal, mais on doit utiliser `foldM`. Voici : +peut plus utiliser le `foldl` normal, mais on doit utiliser `foldM`. +Voici  > ghci> foldM binSmalls 0 [2,8,3,1] > Just 14 @@ -1721,7 +1734,7 @@ avec une pile vide et en utilisant une fonction binaire de pli qui empile les nombres ou manipule ceux au sommet de la pile pour les ajouter ou les diviser, etc. -Ceci était le corps de notre fonction principale : +Ceci était le corps de notre fonction principale  > import Data.List > @@ -1730,7 +1743,7 @@ Ceci était le corps de notre fonction principale : On transformait l'expression en une liste de chaînes de caractères, qu'on pliait avec notre fonction de pli, et il ne nous restait plus qu'un élément -dans la pile, qu'on retournait comme réponse. La fonction de pli était : +dans la pile, qu'on retournait comme réponse. La fonction de pli était  > foldingFunction :: [Double] -> String -> [Double] > foldingFunction (x:y:ys) "*" = (x * y):ys @@ -1747,7 +1760,7 @@ convertissait cette chaîne en un vrai nombre et retournait une nouvelle pile comme l'ancienne, mais avec ce nombre empilé. Rendons d'abord notre fonction de pli capable d'échouer gracieusement. Son type -va changer de ce qu'il était en : +va changer de ce qu'il était en  > foldingFunction :: [Double] -> String -> Maybe [Double] @@ -1760,13 +1773,13 @@ alors elle retourne une liste vide. À part retourner la valeur qu'elle a lue, elle retourne également le morceau de chaîne de caractères qu'elle n'a pas consommé. On va dire qu'il faut qu'elle consomme l'entrée en entier pour que cela marche, et on va créer une fonction `readMaybe` pour notre convenance. La -voici : +voici  > readMaybe :: (Read a) => String -> Maybe a > readMaybe st = case reads st of [(x,"")] -> Just x > _ -> Nothing -Testons : +Testons  > ghci> readMaybe "1" :: Maybe Int > Just 1 @@ -1774,7 +1787,7 @@ Testons : > Nothing Ok, ça a l'air de marcher. Donc, transformons notre fonction de pli en une -fonction monadique pouvant échouer : +fonction monadique pouvant échouer  > foldingFunction :: [Double] -> String -> Maybe [Double] > foldingFunction (x:y:ys) "*" = return ((x * y):ys) @@ -1788,7 +1801,8 @@ aurait tout aussi bien pu écrire `Just`). Dans le dernier cas, on fait `readMaybe numberString` et on mappe ensuite `(:xs)` dessus. Donc, si la pile `xs` est `[1.0, 2.0]` et si `readMaybe numberString` résulte en `Just 3.0`, le résultat est `Just [3.0, 1.0, 2.0]`. Si `readMaybe numberString` résulte en -`Nothing`, alors le résultat est `Nothing`. Essayons la fonction de pli seule : +`Nothing`, alors le résultat est `Nothing`. Essayons la fonction de pli +seule  > ghci> foldingFunction [3,2] "*" > Just [6.0] @@ -1823,7 +1837,7 @@ valeur, ou bien aucune, le filtrage par motif échoue et `Nothing` est produit. À la dernière ligne, on fait simplement `return result` pour présenter le résultat du calcul NPI comme le résultat de la valeur `Maybe`. -Mettons-là à l'essai : +Mettons-là à l'essai  > ghci> solveRPN "1 2 * 4 +" > Just 6.0 @@ -1843,7 +1857,7 @@ lieu parce que `readMaybe` retourne `Nothing`. Lorsque nous étudiions les lois des monades, nous avions dit que la fonction `<=<` était comme la composition, mais qu'au lieu de travailler sur des fonctions ordinaires comme `a -> b`, elle travaillait sur des fonctions comme -`a -> m b`. Par exemple : +`a -> m b`. Par exemple  > ghci> let f = (+1) . (*100) > ghci> f 4 @@ -1858,7 +1872,7 @@ et donné `Just 4` à la fonction résultante à l'aide de `>>=`. Si l'on a tout un tas de fonctions dans une liste, on peut les composer en une unique énorme fonction en utilisant `id` comme accumulateur initial et `.` -comme fonction binaire. Voici un exemple : +comme fonction binaire. Voici un exemple  > ghci> let f = foldr (.) id [(+1),(*100),(+1)] > ghci> f 1 @@ -1877,12 +1891,13 @@ pour trouver si un cavalier pouvait aller d'une position d'un échiquier à une autre en exactement trois mouvements. On avait une fonction nommée `moveKnight` qui prenait la position du cavalier sur l'échiquier et retournait tous les déplacements possibles au prochain tour. Puis, pour générer toutes les -positions possibles après trois mouvements, on a créé la fonction suivante : +positions possibles après trois mouvements, on a créé la fonction +suivante  > in3 start = return start >>= moveKnight >>= moveKnight >>= moveKnight Et pour vérifier s'il pouvait aller de `start` à `end` en trois mouvements, on -faisait : +faisait  > canReachIn3 :: KnightPos -> KnightPos -> Bool > canReachIn3 start end = end `elem` in3 start @@ -1893,7 +1908,7 @@ cavalier après trois mouvements, on peut le faire pour un nombre de mouvements arbitraires. Si vous regardez `in3`, vous voyez qu'on utilise `moveKnight` trois fois, et à chaque fois, on utilise `>>=` pour lui donner toutes les positions précédentes. Rendons cela plus général à présent. Voici comment -procéder : +procéder  > import Data.List > @@ -1908,7 +1923,7 @@ placer la position initiale dans une liste singleton avec `return` et de la donner à notre fonction. On peut maintenant changer la fonctions `canReachIn3` pour être également plus -générale : +générale  > canReachIn :: Int -> KnightPos -> KnightPos -> Bool > canReachIn x start end = end `elem` inMany x start @@ -1941,7 +1956,7 @@ lieu, alors que `5` et `9` n'ont chacun que 25% de chances ? Essayons d'y arriver ! Mettons que chaque élément de la liste vienne avec une valeur supplémentaire, -une probabilité. Il peut sembler logique de le présenter ainsi : +une probabilité. Il peut sembler logique de le présenter ainsi  > [(3,0.5),(5,0.25),(9,0.25)] @@ -1953,7 +1968,7 @@ devenir bordéliques parce qu'ils ont tendance à perdre en précision, ainsi Haskell propose un type de donnée pour les nombres rationnels qui ne perdent pas en précision. Ce type s'appelle `Rational` et vit dans `Data.Ratio`. Pour créer un `Rational`, on l'écrit comme si c'était une fraction. Le numérateur et -le dénominateur sont séparés par un `%`. Voici quelques exemples : +le dénominateur sont séparés par un `%`. Voici quelques exemples  > ghci> 1%4 > 1 % 4 @@ -1965,7 +1980,8 @@ le dénominateur sont séparés par un `%`. Voici quelques exemples : La première ligne est juste un quart. À la deuxième ligne, on additionne deux moitiés, ce qui nous donne un tout, et à la troisième ligne on additione un tiers et cinq quarts et on obtient dix-neuf douzièmes. Jetons ces nombres à -virgule flottante et utilisons plutôt des `Rational` pour nos probabilités : +virgule flottante et utilisons plutôt des `Rational` pour nos +probabilités  > ghci> [(3,1%2),(5,1%4),(9,1%4)] > [(3,1 % 2),(5,1 % 4),(9,1 % 4)] @@ -1984,16 +2000,16 @@ bientôt créer des instances. Bien. Est-ce un foncteur ? Eh bien, la liste étant un foncteur, ceci devrait probablement être également un foncteur, parce qu'on a juste rajouté quelque -chose à la liste. Lorsqu'on mappe une fonction sur une liste, on l'applique -à chaque élément. Ici, on va l'appliquer à chaque élément également, et on -laissera les probabilités comme elles étaient. Créons une instance : +chose à la liste. Lorsqu'on mappe une fonction sur une liste, on l'applique à +chaque élément. Ici, on va l'appliquer à chaque élément également, et on +laissera les probabilités comme elles étaient. Créons une instance  > instance Functor Prob where > fmap f (Prob xs) = Prob $ map (\(x,p) -> (f x,p)) xs On sort la paire du *newtype* en filtrant par motif, on applique la fonction `f` aux valeurs en gardant les probabilités telles quelles, et on réencapsule -le tout. Voyons si cela marche : +le tout. Voyons si cela marche  > ghci> fmap negate (Prob [(3,1%2),(5,1%4),(9,1%4)]) > Prob {getProb = [(-3,1 % 2),(-5,1 % 4),(-9,1 % 4)]} @@ -2021,7 +2037,7 @@ probabilités. Comme exemple, considérons une liste où il y a exactement 25% d chances que `'a'` ou `'b'` ait lieu. `a` et `b` ont la même probabilité. Également, il y a 75% de chances que `c` ou `d` ait lieu. `'c'` et `'d'` ont également la même probabilité. Voici une image de la liste de probabilités qui -modèle ce scénario : +modèle ce scénario  probas @@ -2035,7 +2051,7 @@ quarts multipliés par un demi donnent trois huitièmes `'d'` aurait aussi lieu trois fois sur huit. Si l'on somme toutes les probabilités, la somme vaut toujours un. -Voici cette situation exprimée comme une liste de probabilités : +Voici cette situation exprimée comme une liste de probabilités  > thisSituation :: Prob (Prob Char) > thisSituation = Prob @@ -2047,7 +2063,7 @@ Remarquez que son type est `Prob (Prob Char)`. Maintenant qu'on a trouvé comment aplatir une liste de probabilités imbriquée, il nous suffit d'écrire le code correspondant, et on peut alors écrire `>>=` comme `join (fmap f m)` et avoir notre monade ! Voici `flatten`, qu'on nomme ainsi parce que le nom `join` -est déjà pris : +est déjà pris  > flatten :: Prob (Prob a) -> Prob a > flatten (Prob xs) = Prob $ concat $ map multAll xs @@ -2095,7 +2111,7 @@ Mettons qu'on ait deux pièces normales et une pièce pipée qui donne pile un nombre ahurissant de neuf fois sur dix, et face seulement une fois sur dix. Si l'on jette les trois pièces à la fois, quelles sont les chances qu'elles atterrissent toutes sur pile ? D'abord, créont des valeurs de probabilités pour -un lancer de pièce normale et un lancer de pièce pipée : +un lancer de pièce normale et un lancer de pièce pipée  > data Coin = Heads | Tails deriving (Show, Eq) > @@ -2105,7 +2121,7 @@ un lancer de pièce normale et un lancer de pièce pipée : > loadedCoin :: Prob Coin > loadedCoin = Prob [(Heads,1%10),(Tails,9%10)] -Et finalement, le lancer de pièces en action : +Et finalement, le lancer de pièces en action  > import Data.List (all) > @@ -2117,7 +2133,8 @@ Et finalement, le lancer de pièces en action : > return (all (==Tails) [a,b,c]) En l'essayant, on voit que les chances que les trois pièces atterrissent sur -pile ne sont pas très bonnes, en dépit d'avoir triché avec notre pièce pipée : +pile ne sont pas très bonnes, en dépit d'avoir triché avec notre pièce +pipée  > ghci> getProb flipThree > [(False,1 % 40),(False,9 % 40),(False,1 % 40),(False,9 % 40), @@ -2139,13 +2156,18 @@ assez bonne compréhension de ce que sont les monades.
diff --git a/foncteurs-foncteurs-applicatifs-et-monoides.mkd b/foncteurs-foncteurs-applicatifs-et-monoides.mkd index 4fefc72..bd840aa 100644 --- a/foncteurs-foncteurs-applicatifs-et-monoides.mkd +++ b/foncteurs-foncteurs-applicatifs-et-monoides.mkd @@ -3,13 +3,19 @@ @@ -52,14 +58,14 @@ section](creer-nos-propres-types-et-classes-de-types#la-classe-de-types-functor) Si vous ne l'avez pas encore lue, vous devriez probablement le faire à présent, ou plus tard, quand vous aurez plus de temps. Ou faire semblant de l'avoir lue. -Ceci étant, un petit rappel : les foncteurs sont des choses sur lesquelles on -peut mapper, comme des listes, des `Maybe`, des arbres, et d'autres. En +Ceci étant, un petit rappel  les foncteurs sont des choses sur lesquelles +on peut mapper, comme des listes, des `Maybe`, des arbres, et d'autres. En Haskell, ils sont définis par la classe de types `Functor`, qui n'a qu'une méthode de classe de type, `fmap`, ayant pour type `fmap :: (a -> b) -> f a -> -f b`. Cela dit : donne-moi une fonction qui prend un `a` et retourne un `b`, et -une boîte avec un (ou plusieurs) `a` à l'intérieur, et je te donnerai une boîte -avec un (ou plusieurs) `b` à l'intérieur. Elle applique grosso-modo la fonction -aux éléments dans la boîte. +f b`. Cela dit  donne-moi une fonction qui prend un `a` et retourne un +`b`, et une boîte avec un (ou plusieurs) `a` à l'intérieur, et je te donnerai +une boîte avec un (ou plusieurs) `b` à l'intérieur. Elle applique grosso-modo +la fonction aux éléments dans la boîte.
@@ -127,7 +133,7 @@ fait pas grand chose, mais présente `f result` comme le résultat de l'action I/O composée. On peut jouer un peu avec pour se faire une intuition. C'est en fait assez -simple. Regardez ce code : +simple. Regardez ce code  > main = do line <- getLine > let line' = reverse line @@ -135,7 +141,7 @@ simple. Regardez ce code : > putStrLn $ "Yes, you really said" ++ line' ++ " backwards!" On demande une ligne à l'utilisateur, et on la lui rend, mais renversée. Voici -comment réécrire ceci en utilisant `fmap` : +comment réécrire ceci en utilisant `fmap`  > main = do line <- fmap reverse getLine > putStrLn $ "You said " ++ line ++ " backwards!" @@ -166,7 +172,7 @@ appliquer une fonction à ce nom et lui donner un nouveau nom, utilisez plutôt `fmap`, ce sera plus joli. Si vous voulez appliquez des transformations multiples à une donnée dans un foncteur, vous pouvez soit déclarer une fonction dans l'espace de nom global, soit utiliser une lambda expression, ou -idéalement, utiliser la composition de fonctions : +idéalement, utiliser la composition de fonctions  > import Data.Char > import Data.List @@ -211,7 +217,7 @@ lettres pour les variables de type. > instance Functor ((->) r) where > fmap f g = (\x -> f (g x)) -Si la syntaxe le permettait, on aurait pu écrire : +Si la syntaxe le permettait, on aurait pu écrire  > instance Functor (r ->) where > fmap f g = (\x -> f (g x)) @@ -236,7 +242,7 @@ fonctions ! On connecte la sortie de `r -> a` à l'entrée de `a -> b` pour obtenir une fonction `r -> b`, ce qui est exactement ce que fait la composition de fonctions. Si vous regardez comment l'instance est définie ci-dessus, vous verrez qu'on a juste composé les fonctions. Une autre façon d'écrire cette -instance serait : +instance serait  > instance Functor ((->) r) where > fmap = (.) @@ -301,7 +307,7 @@ foncteur pour retourner un foncteur, mais plutôt comme une fonction qui prend une fonction, et retourne une nouvelle fonction, similaire à l'ancienne, mais qui prend et retourne des foncteurs. Elle prend une fonction `a -> b`, et retourne une fonction `f a -> f b`. On dit qu'on *lifte* la fonction. Jouons -avec cette idée en utilisant la commande `:t` de GHCi : +avec cette idée en utilisant la commande `:t` de GHCi  > ghci> :t fmap (*2) > fmap (*2) :: (Num a, Functor f) => f a -> f a @@ -406,8 +412,8 @@ trivial. De ces deux équations de l'implémentation de `fmap`, on déduit que foncteur doit être identique à mapper d'abord une des fonctions sur le foncteur, puis mapper l'autre sur le résultat.** Formellement, on veut fmap (f . g) = fmap f . fmap g. Ou, d'une autre façon, pour -tout foncteur F, on souhaite : fmap (f . g) F = fmap f (fmap -g F). +tout foncteur F, on souhaite  fmap (f . g) F = fmap f +(fmap g F). Si l'on peut montrer qu'un type obéit à ces deux lois des foncteurs, alors on peut avoir l'assurance qu'il aura les mêmes propriétés fondamentales vis-à-vis @@ -440,7 +446,7 @@ sur un tas de valeurs et vous convaincre que le type suit bien les lois. Intéressons-nous au cas pathologique d'un constructeur de types instance de `Functor` mais qui n'est pas vraiment un foncteur, parce qu'il ne satisfait pas -les lois. Mettons qu'on ait un type : +les lois. Mettons qu'on ait un type  > data CMaybe a = CNothing | CJust Int a deriving (Show) @@ -475,7 +481,7 @@ incrémenté de 1. C'est un peu comme l'implémentation de `Maybe`, à l'exception que lorsqu'on fait `fmap` sur une valeur qui n'est pas une boîte vide (donc sur une valeur `CJust`), en plus d'appliquer la fonction au contenu, on augmente le compteur -de 1. Tout va bien pour l'instant, on peut même jouer un peu avec : +de 1. Tout va bien pour l'instant, on peut même jouer un peu avec  > ghci> fmap (++"ha") (CJust 0 "ho") > CJust 1 "hoha" @@ -623,7 +629,7 @@ Je vous présente la classe de types `Applicative`. Elle réside dans le module `Control.Applicative` et définit deux méthodes, `pure` et `<*>`. Elle ne fournit pas d'implémentation par défaut pour celles-ci, il faut donc les définir toutes deux nous-même si l'on veut faire de quelque chose un foncteur -applicatif. La classe est définie ainsi : +applicatif. La classe est définie ainsi  > class (Functor f) => Applicative f where > pure :: a -> f a @@ -719,7 +725,7 @@ Avec des foncteurs normaux, on peut juste mapper une fonction sur un foncteur, et on ne peut plus récupérer ce résultat hors du foncteur de manière générale, même lorsque le résultat est une fonction appliquée partiellement. Les foncteurs applicatifs, quant à eux, permettent d'opérer sur plusieurs foncteurs -avec la même fonction. Regardez ce bout de code : +avec la même fonction. Regardez ce bout de code  > ghci> pure (+) <*> Just 3 <*> Just 5 > Just 8 @@ -759,19 +765,19 @@ a fait la même chose que de juste mapper cette fonction sur le second foncteur applicatif. Plutôt que d'écrire `pure f <*> x <*> y <*> …`, on peut écrire `fmap f x <*> y <*> …`. C'est pourquoi `Control.Applicative` exporte une fonction `<$>` qui est simplement `fmap` en tant qu'opérateur infixe. Voici sa -définition : +définition  > (<$>) :: (Functor f) => (a -> b) -> f a -> f b > f <$> x = fmap f x
-**Yo !** Petit rappel : les variables de types sont indépendentes des noms des -paramètres ou des noms des valeurs en général. Le `f` de la déclaration de la -fonction ici est une variable de type avec une contrainte de classe disant que -tout type remplaçant `f` doit être membre de la classe `Functor`. Le `f` dans -le corps de la fonction dénote une fonction qu'on mappe sur `x`. Le fait que -`f` soit utilisé pour représenter ces deux choses ne signifie pas qu'elles +**Yo !** Petit rappel  les variables de types sont indépendentes des noms +des paramètres ou des noms des valeurs en général. Le `f` de la déclaration de +la fonction ici est une variable de type avec une contrainte de classe disant +que tout type remplaçant `f` doit être membre de la classe `Functor`. Le `f` +dans le corps de la fonction dénote une fonction qu'on mappe sur `x`. Le fait +que `f` soit utilisé pour représenter ces deux choses ne signifie pas qu'elles représentent la même chose.
@@ -783,12 +789,13 @@ applicatifs mais des valeurs normales, on aurait écrit `f x y z`. Regardons cela de plus près. On a une valeur `Just "johntra"` et une valeur `Just "volta"`, et on veut joindre les deux en une `String` dans un foncteur -`Maybe`. On fait cela : +`Maybe`. On fait cela  > ghci> (++) <$> Just "johntra" <*> Just "volta" > Just "johntravolta" -Avant qu'on se penche là-dessus, comparez la ligne ci-dessus avec celle-ci : +Avant qu'on se penche là-dessus, comparez la ligne ci-dessus avec +celle-ci  > ghci> (++) "johntra" "volta" > "johntravolta" @@ -812,7 +819,7 @@ beaucoup d'autres instances d'`Applicative`, alors découvrons en plus ! Les listes (ou plutôt, le constructeur de types listes, `[]`) sont des foncteurs applicatifs. Quelle surprise ! Voici l'instance d'`Applicative` de -`[]` : +`[]`  > instance Applicative [] where > pure x = [x] @@ -863,7 +870,7 @@ premier, résultant en une liste équivalente à `[(1+), (2+), (1*), (2*)]`, par que chaque fonction à gauche est appliquée sur chaque valeur de droite. Puis, `[(1+),(2+),(1*),(2*)] <*> [3,4]` est exécuté, produisant le résultat final. -Utiliser le style applicatif avec les listes est fun ! Regardez : +Utiliser le style applicatif avec les listes est fun ! Regardez  > ghci> (++) <$> ["ha","heh","hmm"] <*> ["?","!","."] > ["ha?","ha!","ha.","heh?","heh!","heh.","hmm?","hmm!","hmm."] @@ -883,20 +890,20 @@ calcul non déterministe encore moins certain de son résultat. Le style applicatif sur les listes est souvent un bon remplaçant des listes en compréhension. Dans le deuxième chapitre, on souhaitait connaître tous les -produits possibles de `[2, 5, 10]` et `[8, 10, 11]`, donc on a fait : +produits possibles de `[2, 5, 10]` et `[8, 10, 11]`, donc on a fait  > ghci> [ x*y | x <- [2,5,10], y <- [8,10,11]] > [16,20,22,40,50,55,80,100,110] On pioche simplement dans deux listes et on applique la fonction à toutes les -combinaisons d'éléments. Cela peut être fait dans le style applicatif : +combinaisons d'éléments. Cela peut être fait dans le style applicatif  > ghci> (*) <$> [2,5,10] <*> [8,10,11] > [16,20,22,40,50,55,80,100,110] Cela me paraît plus clair, parce qu'il est plus simple de voir qu'on appelle seulement `*` entre deux calculs non déterministes. Si l'on voulait tous les -produits possibles de deux listes supérieurs à 50, on ferait : +produits possibles de deux listes supérieurs à 50, on ferait  > ghci> filter (>50) $ (*) <$> [2,5,10] <*> [8,10,11] > [55,80,100,110] @@ -907,7 +914,7 @@ la liste de gauche à chaque valeur de la liste de droite, mais puisqu'il n'y a qu'une fonction à gauche, c'est comme mapper. Une autre instance d'`Applicative` qu'on a déjà rencontrée est `IO`. Voici -comment son instance est implémentée : +comment son instance est implémentée  > instance Applicative IO where > pure = return @@ -920,7 +927,7 @@ comment son instance est implémentée : Puisque `pure` ne fait que mettre des valeurs dans un contexte minimal qui puisse toujours renvoyer ce résultat, il est sensé que `pure` soit juste -`return`, parce que c'est exactement ce que fait `return` : elle crée une +`return`, parce que c'est exactement ce que fait `return`  elle crée une action I/O qui ne fait rien, mais retourne la valeur passée en résultat, sans rien écrire sur le terminal ni écrire dans un fichier. @@ -942,7 +949,7 @@ actions I/O et qu'on les ordonne, en les collant l'une à l'autre. On doit extraire la fonction de la première action I/O, mais pour pouvoir l'extraire, il faut exécuter l'action. -Considérez ceci : +Considérez ceci  > myAction :: IO String > myAction = do @@ -953,7 +960,7 @@ Considérez ceci : C'est une action I/O qui demande à l'utilisateur d'entrer deux lignes, et retourne en résultat ces deux lignes concaténées. Ceci est obtenu en collant deux actions I/O `getLine` ensemble avec un `return`, afin que le résultat soit -`a ++ b`. Une autre façon d'écrire cela en style applicatif serait : +`a ++ b`. Une autre façon d'écrire cela en style applicatif serait  > myAction :: IO String > myAction = (++) <$> getLine <*> getLine @@ -967,7 +974,7 @@ est un foncteur applicatif, donc tout va bien. Le type de l'expression `(++) <$> getLine <*> getLine` est `IO String`, ce qui signifie que cette expression est une action I/O comme une autre, qui contient également une valeur résultante, comme toutes les actions I/O. C'est pourquoi -on peut faire : +on peut faire  > main = do > a <- (++) <$> getLine <*> getLine @@ -1071,7 +1078,7 @@ pouvez l'imaginer comme `[1 + 3, 2 * 2]`. Puisqu'un type ne peut pas avoir deux instances de la même classe de types, le type `ZipList a` est introduit, et il a pour seul constructeur `ZipList` qui ne -prend qu'un seul champ, de type liste. Voici son instance : +prend qu'un seul champ, de type liste. Voici son instance  > instance Applicative ZipList where > pure x = ZipList (repeat x) @@ -1128,7 +1135,7 @@ nombre arbitraire de listes avec une fonction, c'est plutôt cool. `Control.Applicative` définit une fonction nommée `liftA2`, qui a pour type `liftA2 :: (Applicative f) => (a -> b -> c) -> f a -> f b -> f c`. Elle est -définie ainsi : +définie ainsi  > liftA2 :: (Applicative f) => (a -> b -> c) -> f a -> f b -> f c > liftA2 f a b = f <$> a <*> b @@ -1144,11 +1151,11 @@ fonction comme `(a -> b -> c) -> (f a -> f b -> f c)`. Quand on regarde de cette façon, on voit que `liftA2` prend une fonction binaire normale, et la promeut en une fonction binaire sur deux foncteurs. -Voici un concept intéressant : on peut prendre deux foncteurs applicatifs, et -les combiner en un foncteur applicatif qui contient en lui les résultats de ces -deux foncteurs applicatifs, sous forme d'une liste. Par exemple, on a `Just 3` -et `Just 4`. Imaginons que ce deuxième a une liste singleton en lui, puisque -c'est très simple à réaliser : +Voici un concept intéressant  on peut prendre deux foncteurs applicatifs, +et les combiner en un foncteur applicatif qui contient en lui les résultats de +ces deux foncteurs applicatifs, sous forme d'une liste. Par exemple, on a `Just +3` et `Just 4`. Imaginons que ce deuxième a une liste singleton en lui, puisque +c'est très simple à réaliser  > ghci> fmap (\x -> [x]) (Just 4) > Just [4] @@ -1242,7 +1249,7 @@ en une liste, qui est le résultat de cette fonction. Utiliser `sequenceA` est cool quand on a une liste de fonctions et qu'on veut leur donner la même entrée et voir une liste des résultats. Par exemple, si l'on a un nombre et qu'on se demande s'il satisfait tous les prédicats d'une -liste. Un moyen de faire serait le suivant : +liste. Un moyen de faire serait le suivant  > ghci> map (\f -> f 7) [(>4),(<10),odd] > [True,True,True] @@ -1250,7 +1257,7 @@ liste. Un moyen de faire serait le suivant : > True Souvenez-vous, `and` prend une liste de booléens et ne retourne `True` que -s'ils sont tous `True`. Un autre moyen de faire ceci, avec `sequenceA` : +s'ils sont tous `True`. Un autre moyen de faire ceci, avec `sequenceA`  > ghci> sequenceA [(>4),(<10),odd] 7 > [True,True,True] @@ -1271,7 +1278,7 @@ avoir le même type, évidemment. Vous ne pouvez pas avoir une liste comme `[ord Quand on l'utilise avec `[]`, `sequenceA` prend une liste de listes et retourne une liste de listes. Hmm, intéressant. Elle crée en fait des listes contenant toutes les combinaisons possibles des éléments. Par exemple, voici ceci réalisé -avec `sequenceA` puis avec une liste en compréhension : +avec `sequenceA` puis avec une liste en compréhension  > ghci> sequenceA [[1,2,3],[4,5,6]] > [[1,4],[1,5],[1,6],[2,4],[2,5],[2,6],[3,4],[3,5],[3,6]] @@ -1344,7 +1351,7 @@ Comme les foncteurs ordinaires, les foncteurs applicatifs viennent avec quelques lois. La plus importante est celle qu'on a déjà mentionnée, qui dit que pure f <*> x = fmap f x. En exercice, vous pouvez prouver cette loi pour quelques uns des foncteurs applicatifs vus dans ce -chapitre. Les autres lois des foncteurs applicatifs sont : +chapitre. Les autres lois des foncteurs applicatifs sont  * pure id <*> v = v @@ -1400,21 +1407,21 @@ champ. On place la liste qu'on enveloppe dans ce champ. Ainsi, `ZipList` est fait instance d'`Applicative`, de manière à ce que si l'on souhaite utiliser des listes comme foncteurs applicatifs de la manière zip, on ait juste à envelopper la liste dans le constructeur `ZipList`, puis, une fois les calculs -terminés, là sortir avec `getZipList` : +terminés, là sortir avec `getZipList`  > ghci> getZipList $ ZipList [(+1),(*100),(*5)] <*> ZipList [1,2,3] > [2,200,15] Donc, que fait le nouveau mot-clé *newtype* ? Eh bien, réfléchissez à la façon dont vous écririez la déclaration *data* du type `ZipList a`. Une façon de -l'écrire serait : +l'écrire serait  > data ZipList a = ZipList [a] Un type qui n'a qu'un constructeur de valeurs, et ce constructeur de valeurs n'a qu'un champ, qui est une liste de choses. On aurait aussi pu vouloir utiliser la syntaxe des enregistrements pour obtenir automatiquement une -fonction qui extrait une liste d'une `ZipList` : +fonction qui extrait une liste d'une `ZipList`  > data ZipList a = ZipList { getZipList :: [a] } @@ -1425,7 +1432,8 @@ type, et fait de ce second type une instance de la seconde manière. Le mot-clé *newtype* en Haskell est fait exactement pour ces cas où l'on veut juste prendre un type et l'encapsuler dans quelque chose pour le présenter -comme un nouveau type. Dans la bibliothèque, `ZipList a` est définie comme : +comme un nouveau type. Dans la bibliothèque, `ZipList a` est définie +comme  > newtype ZipList a = ZipList { getZipList :: [a] } @@ -1445,7 +1453,7 @@ Pourquoi ne pas utiliser *newtype* tout le temps plutôt que *data* dans ce cas utilisant le mot-clé *newtype*, vous ne pouvez avoir qu'un seul constructeur de valeurs, et ce constructeur de valeurs ne peut avoir qu'un seul champ. Alors qu'avec *data*, vous pouvez créer des types de données qui ont plusieurs -constructeurs de valeurs, chacun pouvant avoir zéro ou plusieurs champs : +constructeurs de valeurs, chacun pouvant avoir zéro ou plusieurs champs  > data Profession = Fighter | Archer | Accountant > @@ -1461,11 +1469,11 @@ fait avec *data*. On peut dériver les instances d'`Eq`, `Ord`, `Enum`, `Bounded`, `Show` et `Read`. Si l'on souhaite dériver une instance d'une classe de types, le type qu'on enveloppe doit lui-même être membre de cette classe. C'est logique, parce que *newtype* ne fait qu'envelopper ce type. On peut donc -afficher et tester l'égalité de valeurs de notre nouveau type en faisant : +afficher et tester l'égalité de valeurs de notre nouveau type en faisant  > newtype CharList = CharList { getCharList :: [Char] } deriving (Eq, Show) -Essayons : +Essayons  > ghci> CharList "this will be shown!" > CharList {getCharList = "this will be shown!"} @@ -1474,7 +1482,7 @@ Essayons : > ghci> CharList "benny" == CharList "oisters" > False -Pour ce *newtype*, le constructeur de valeurs a pour type : +Pour ce *newtype*, le constructeur de valeurs a pour type  > CharList :: [Char] -> CharList @@ -1482,7 +1490,7 @@ Il prend une valeur `[Char]`, comme `"my sharona"`, et retourne une valeur `CharList`. Dans les exemples ci-dessus où l'on utilisait le constructeur de valeurs `CharList`, on voit que c'est le cas. Réciproquement, la fonction `getCharList`, qui a été générée lorsqu'on a utilisé la syntaxe des -enregistrements dans notre *newtype*, a pour type : +enregistrements dans notre *newtype*, a pour type  > getCharList :: CharList -> [Char] @@ -1495,18 +1503,19 @@ comme une conversion d'un type vers l'autre. Souvent, on veut faire de nos types des instances de certaines classes de types, mais les paramètres de types ne correspondent pas à ce que l'on souhaite. Il est facile de faire une instance de `Functor` pour `Maybe`, parce -que la classe de types `Functor` est définie comme : +que la classe de types `Functor` est définie comme  > class Functor f where > fmap :: (a -> b) -> f a -> f b -Donc on peut démarrer en faisant : +Donc on peut démarrer en faisant  > instance Functor Maybe where Et implémenter `fmap`. Tous les paramètres de type s'alignent parce que `Maybe` -vient prendre la place de `f` dans la définition de la classe de types `Functor` -et donc, si l'on considère `fmap` comme vu des `Maybe`, elle se comporte ainsi : +vient prendre la place de `f` dans la définition de la classe de types +`Functor` et donc, si l'on considère `fmap` comme vu des `Maybe`, elle se +comporte ainsi  > fmap :: (a -> b) -> Maybe a -> Maybe b @@ -1522,12 +1531,12 @@ que seuls les constructeurs de types prenant exactement un paramètre peuvent comme `(a, b)` de manière à ce que ce soit `a` qui change quand on utilise `fmap`. Pour contourner ce problème, on peut envelopper notre tuple dans un *newtype* de manière à ce que le deuxième paramètre de type représente la -première composante du tuple : +première composante du tuple  > newtype Pair b a = Pair { getPair :: (a,b) } À présent, on peut en faire une instance de `Functor` qui mappe la fonction sur -la première composante : +la première composante  > instance Functor (Pair c) where > fmap f (Pair (x,y)) = Pair (f x, y) @@ -1536,18 +1545,18 @@ Comme vous le voyez, on peut filtrer par motif les types définis par *newtype*. On filtre par motif pour obtenir le tuple sous-jacent, puis on applique `f` à la première composante de ce tuple, et on utilise le constructeur de valeurs `Pair` pour convertir à nouveau le tuple en `Pair b a`. Le type de `fmap` si -elle ne marchait que sur nos paires serait : +elle ne marchait que sur nos paires serait  > fmap :: (a -> b) -> Pair c a -> Pair c b -À nouveau, on a dit `instance Functor (Pair c) where`, et ainsi, `Pair c` -prend la place de `f` dans la définition de classe de `Functor` : +À nouveau, on a dit `instance Functor (Pair c) where`, et ainsi, `Pair c` prend +la place de `f` dans la définition de classe de `Functor`  > class Functor f where > fmap :: (a -> b) -> f a -> f b Maintenant, si l'on convertit un tuple en une `Pair b a`, on peut utiliser -`fmap` sur celle-ci et la fonction sera mappée sur la première composante : +`fmap` sur celle-ci et la fonction sera mappée sur la première composante  > ghci> getPair $ fmap (*100) (Pair (2,3)) > (200,3) @@ -1570,7 +1579,7 @@ fonctions que ceux-ci sont calculés. De plus, seuls les calculs nécessaires pour trouver le résultat de notre fonction sont exécutés. La valeur `undefined` en Haskell représente un calcul erroné. Si on essaie de l'évaluer (c'est-à-dire, de forcer Haskell à la calculer) en l'affichant sur le terminal, -Haskell piquera une crise (ou en termes techniques, lèvera une exception) : +Haskell piquera une crise (ou en termes techniques, lèvera une exception)  > ghci> undefined > *** Exception: Prelude.undefined @@ -1578,19 +1587,19 @@ Haskell piquera une crise (ou en termes techniques, lèvera une exception) : Cependant, si on crée une liste qui contient quelques valeurs `undefined` mais qu'on ne demande que sa tête, qui n'est pas `undefined`, tout se passera bien parce qu'Haskell n'a pas besoin d'évaluer les autres éléments de la liste pour -trouver son premier élément : +trouver son premier élément  > ghci> head [3,4,5,undefined,2,undefined] > 3 -À présent, considérez ce type : +À présent, considérez ce type  > data CoolBool = CoolBool { getCoolBool :: Bool } Un type de données algébrique tout droit sorti de l'usine, défini via le mot-clé *data*. Il n'a qu'un constructeur de valeurs, qui n'a qu'un champ de type `Bool`. Créons une fonction qui filtre par motif un `CoolBool` et retourne -la valeur `"hello"` quelle que soit la valeur du booléen dans `CoolBool` : +la valeur `"hello"` quelle que soit la valeur du booléen dans `CoolBool`  > helloMe :: CoolBool -> String > helloMe (CoolBool _) = "hello" @@ -1609,13 +1618,14 @@ l'évaluer juste assez pour voir quel constructeur de valeur a été utilisé po la créer. Et lorsqu'on essaie d'évaluer la valeur `undefined`, même un tout petit peu, une exception est levée. -Plutôt que le mot-clé *data* pour `CoolBool`, essayons d'utiliser *newtype* : +Plutôt que le mot-clé *data* pour `CoolBool`, essayons d'utiliser +*newtype*  > newtype CoolBool = CoolBool { getCoolBool :: Bool } Pas besoin de changer la fonction `helloMe`, parce que la syntaxe de filtrage par motif est la même pour *newtype* que pour *data*. Faisons-la même chose ici -et appliquons `helloMe` à une valeur `undefined` : +et appliquons `helloMe` à une valeur `undefined`  > ghci> helloMe undefined > "hello" @@ -1649,7 +1659,7 @@ un type en un autre immédiatement. Le mot-clé **type** crée des synonymes de types. Cela signifie qu'on donne simplement un autre nom à un type existant, pour qu'il soit plus simple d'en -parler. Par exemple, ceci : +parler. Par exemple, ceci  > type IntList = [Int] @@ -1657,7 +1667,7 @@ Tout ce que cela fait, c'est nous permettre de faire référence à `[Int]` en tapant `IntList`. Les deux peuvent être utilisés de manière interchangeable. On n'obtient pas de constructeur de valeurs `IntList` ou quoi que ce soit du genre. Puisque `[Int]` et `IntList` sont deux façons de parler du même type, -peu importe laquelle on utilise dans nos annotations de types : +peu importe laquelle on utilise dans nos annotations de types  > ghci> ([1,2,3] :: IntList) ++ ([1,2,3] :: [Int]) > [1,2,3,1,2,3] @@ -1674,7 +1684,7 @@ Le mot-clé **newtype** sert à prendre des types existants et les emballer dans de nouveaux types, principalement parce que ça facilite la création d'instances de certaines classes de types. Quand on utilise *newtype* pour envelopper un type existant, le type qu'on obtient est différent du type original. Si l'on -crée le nouveau type suivant : +crée le nouveau type suivant  > newtype CharList = CharList { getCharList :: [Char] } @@ -1687,7 +1697,7 @@ celles-ci, puis les reconvertir en `CharList`. Quand on utilise la syntaxe des enregistrements dans notre déclaration *newtype*, on obtient des fonctions pour convertir entre le type original et le -nouveau type : dans une sens, avec le constructeur de valeurs de notre +nouveau type  dans une sens, avec le constructeur de valeurs de notre *newtype*, et dans l'autre sens, avec la fonction qui extrait la valeur du champ. Le nouveau type n'est pas automatiquement fait instance des classes de types du type original, donc il faut les dériver ou les écrire manuellement. @@ -1726,13 +1736,14 @@ classes de types on en fera une instance. Si cela a un sens de tester l'égalit de nos valeurs, alors on crée une instance d'`Eq`. Si l'on voit que notre type est un foncteur, alors on crée une instance de `Functor`, et ainsi de suite. -Maintenant, considérez cela : `*` est une fonction qui prend deux nombres et -les multiplie entre eux. Si l'on multiplie un nombre par `1`, le résultat est -toujours égal à ce nombre. Peu importe qu'on fasse `1 * x` ou `x * 1`, le +Maintenant, considérez cela  `*` est une fonction qui prend deux nombres +et les multiplie entre eux. Si l'on multiplie un nombre par `1`, le résultat +est toujours égal à ce nombre. Peu importe qu'on fasse `1 * x` ou `x * 1`, le résultat est toujours `x`. De façon similaire, `++` est aussi une fonction qui prend deux choses et en retourne une troisième. Au lieu de les multiplier, elle les concatène. Et tout comme `*`, elle a aussi une valeur qui ne change pas -l'autre quand on la combine avec `++`. Cette valeur est la liste vide : `[]`. +l'autre quand on la combine avec `++`. Cette valeur est la liste vide  +`[]`. > ghci> 4 * 1 > 4 @@ -1743,7 +1754,8 @@ l'autre quand on la combine avec `++`. Cette valeur est la liste vide : `[]`. > ghci> [] ++ [0.5, 2.5] > [0.5,2.5] -On dirait que `*` et `1` partagent une propriété commune avec `++` et `[]` : +On dirait que `*` et `1` partagent une propriété commune avec `++` et +`[]`  * La fonction prend deux paramètres. @@ -1752,11 +1764,11 @@ On dirait que `*` et `1` partagent une propriété commune avec `++` et `[]` : * Il existe une valeur de ce type qui ne change pas l'autre valeur lorsqu'elle est appliquée à la fonction binaire. -Autre chose que ces deux opérations ont en commun et qui n'est pas si évident : -lorsqu'on a trois valeurs ou plus et qu'on utilise la fonction binaire pour les -réduire à une seule valeur, l'ordre dans lequel on applique la fonction -n'importe pas. Peu importe qu'on fasse `(3 * 4) * 5` ou `3 * (4 * 5)`. De toute -manière, le résultat est `60`. De même pour `++` : +Autre chose que ces deux opérations ont en commun et qui n'est pas si +évident  lorsqu'on a trois valeurs ou plus et qu'on utilise la fonction +binaire pour les réduire à une seule valeur, l'ordre dans lequel on applique la +fonction n'importe pas. Peu importe qu'on fasse `(3 * 4) * 5` ou `3 * (4 * 5)`. +De toute manière, le résultat est `60`. De même pour `++`  > ghci> (3 * 2) * (8 * 5) > 240 @@ -1807,13 +1819,13 @@ Ensuite, on a `mappend`, qui, comme vous l'avez probablement deviné, est la fonction binaire. Elle prend deux valeurs du même type et retourne une valeur de ce type. Il est bon de mentionner que la décision d'appeler `mappend` de la sorte est un peu malheureuse, parce que ce nom semble impliquer qu'on essaie de -juxtaposer deux choses (NDT : en anglais, "append" signifie "juxtaposer"). Bien -que `++` prenne deux listes et les juxtapose, `*` ne juxtapose pas vraiment, -elle multiplie seulement deux nombres. Quand nous rencontrerons d'autres -instances de `Monoid`, on verra que la plupart d'entre elles ne juxtaposent -pas non plus leurs valeurs, évitez donc de penser en termes de juxtaposition, -et pensez plutôt que `mappend` est une fonction binaire qui prend deux valeurs -monoïdales et en retourne une troisième. +juxtaposer deux choses (NDT  en anglais, "append" signifie "juxtaposer"). +Bien que `++` prenne deux listes et les juxtapose, `*` ne juxtapose pas +vraiment, elle multiplie seulement deux nombres. Quand nous rencontrerons +d'autres instances de `Monoid`, on verra que la plupart d'entre elles ne +juxtaposent pas non plus leurs valeurs, évitez donc de penser en termes de +juxtaposition, et pensez plutôt que `mappend` est une fonction binaire qui +prend deux valeurs monoïdales et en retourne une troisième. La dernière fonction de la définition de la classe de types est `mconcat`. Elle prend une liste de valeurs monoïdales et les réduit à une unique valeur en @@ -1833,7 +1845,7 @@ associative. Il est possible de créer des instances de `Monoid` ne suivant pas ces règles, mais ces instances ne servent à personne parce que, quand on utilise la classe de types `Monoid`, on se repose sur le fait que ses instances agissent comme des monoïdes. Sinon, à quoi cela servirait-il ? C'est pourquoi, -en créant des instances, on doit s'assurer qu'elles obéissent à ces lois : +en créant des instances, on doit s'assurer qu'elles obéissent à ces lois  * mempty `mappend` x = x @@ -1851,7 +1863,7 @@ programmeur de faire attention à ce que nos instances y obéissent.

Les listes sont des monoïdes

Oui, les listes sont des monoïdes ! Comme on l'a vu, la fonction `++` et la -liste vide `[]` forment un monoïde. L'instance est très simple : +liste vide `[]` forment un monoïde. L'instance est très simple  > instance Monoid [a] where > mempty = [] @@ -1862,7 +1874,7 @@ type des éléments qu'elles contiennent. Remarquez qu'on a écrit `instance Monoid [a]` et pas `instance Monoid []`, parce que `Monoid` nécessite un type concret en instance. -En testant cela, pas de surprises : +En testant cela, pas de surprises  > ghci> [1,2,3] `mappend` [4,5,6] > [1,2,3,4,5,6] @@ -1939,13 +1951,14 @@ de la même classe de types, on peut l'envelopper dans un *newtype* et faire de ce nouveau type une autre instance de la même classe de types. On a le beurre et l'argent du beurre. -Le module `Data.Monoid` exporte deux types pour cela, `Product` et `Sum`. `Product` est défini comme : +Le module `Data.Monoid` exporte deux types pour cela, `Product` et `Sum`. +`Product` est défini comme  > newtype Product a = Product { getProduct :: a } > deriving (Eq, Ord, Read, Show, Bounded) Simple, un enrobage avec *newtype*, avec un paramètre de type et quelques -instance dérivées. Son instance de `Monoid` ressemble à : +instance dérivées. Son instance de `Monoid` ressemble à  > instance Num a => Monoid (Product a) where > mempty = Product 1 @@ -1974,7 +1987,7 @@ verrons comment ces instances de `Monoid` qui peuvent sembler triviales s'avèrent pratiques. `Sum` est défini comme `Product` et l'instance est également similaire. On -l'utilise ainsi : +l'utilise ainsi  > ghci> getSum $ Sum 2 `mappend` Sum 9 > 11 @@ -1991,21 +2004,22 @@ fonction binaire, avec `False` pour élément neutre. En logique, *ou* est `True si n'importe lequel de ses paramètres est `True`, sinon c'est `False`. Ainsi, en utilisant `False` comme élément neutre, on retourne bien `False` en faisant *ou* avec `False`, et `True` en faisant *ou* avec `True`. Le *newtype* `Any` -est une instance de monoïde de cette manière. Il est défini ainsi : +est une instance de monoïde de cette manière. Il est défini ainsi  > newtype Any = Any { getAny :: Bool } > deriving (Eq, Ord, Read, Show, Bounded) -Quant à l'instance : +Quant à l'instance  > instance Monoid Any where > mempty = Any False > Any x `mappend` Any y = Any (x || y) Il s'appelle `Any` parce que x \`mappend\` y sera `True` si -n'importe laquelle (NDT : en anglais, *any*) de ses valeurs est `True`. Même si -trois ou plus `Bool` enveloppés dans `Any` sont `mappend` ensemble, le résultat -sera toujours `True` lorsque n'importe lequel d'entre eux est `True` : +n'importe laquelle (NDT  en anglais, *any*) de ses valeurs est `True`. +Même si trois ou plus `Bool` enveloppés dans `Any` sont `mappend` ensemble, le +résultat sera toujours `True` lorsque n'importe lequel d'entre eux est +`True`  > ghci> getAny $ Any True `mappend` Any False > True @@ -2019,20 +2033,20 @@ sera toujours `True` lorsque n'importe lequel d'entre eux est `True` : L'autre façon de faire une instance de `Monoid` pour `Bool` est un peu l'opposé : utiliser `&&` comme fonction binaire et `True` comme élément neutre. Le *et* logique retourne `True` si ses deux paramètres valent `True`. Voici la -déclaration *newtype*, rien d'extraordinaire : +déclaration *newtype*, rien d'extraordinaire  > newtype All = All { getAll :: Bool } > deriving (Eq, Ord, Read, Show, Bounded) -Et l'instance : +Et l'instance  > instance Monoid All where > mempty = All True > All x `mappend` All y = All (x && y) Quand on `mappend` des valeurs de type `All`, le résultat sera `True` seulement -si toutes (NDT : en anglais, *all*), les valeurs passées à `mappend` sont -`True` : +si toutes (NDT  en anglais, *all*), les valeurs passées à `mappend` sont +`True`  > ghci> getAll $ mempty `mappend` All True > True @@ -2053,8 +2067,8 @@ respectivement, l'une d'elles est `True` ou toutes sont `True`.

Le monoïde `Ordering`

Hé, vous vous souvenez du type `Ordering` ? C'est le type retourné lorsqu'on -compare des choses, qui peut avoir trois valeurs : `LT`, `EQ` et `GT`, qui -signifient *inférieur*, *égal* et *supérieur* respectivement : +compare des choses, qui peut avoir trois valeurs  `LT`, `EQ` et `GT`, qui +signifient *inférieur*, *égal* et *supérieur* respectivement  > ghci> 1 `compare` 2 > LT @@ -2068,7 +2082,7 @@ monoïdes est juste l'affaire de regarder ce qu'on utilisait déjà afin de voir si ces choses présentent un comportement monoïdal. Pour `Ordering`, il faut y regarder à deux fois avant de reconnaître le monoïde, mais il s'avère que sont instance de `Monoid` est tout aussi intuitive que celles recontrées -précédemment, et plutôt utile également : +précédemment, et plutôt utile également  > instance Monoid Ordering where > mempty = EQ @@ -2078,14 +2092,15 @@ précédemment, et plutôt utile également : ours -L'instance est créée de la sorte : quand on `mappend` deux valeurs `Ordering`, -celle de gauche est préservée, à moins que la valeur à gauche soit `EQ`, auquel -cas celle de droite est le résultat. Le neutre est `EQ`. Au départ, ça peut -sembler arbitraire, mais cela ressemble en fait beaucoup à la façon donc on -compare lexicographiquement des mots. On compare d'abord les premières lettres -de chaque mot, et si elles diffèrent, on peut immédiatement décider de l'ordre -des mots dans un dictionnaire. Cependant, si elles sont égales, alors on doit -comparer la prochaine paire de lettres et répéter le procédé. +L'instance est créée de la sorte  quand on `mappend` deux valeurs +`Ordering`, celle de gauche est préservée, à moins que la valeur à gauche soit +`EQ`, auquel cas celle de droite est le résultat. Le neutre est `EQ`. Au +départ, ça peut sembler arbitraire, mais cela ressemble en fait beaucoup à la +façon donc on compare lexicographiquement des mots. On compare d'abord les +premières lettres de chaque mot, et si elles diffèrent, on peut immédiatement +décider de l'ordre des mots dans un dictionnaire. Cependant, si elles sont +égales, alors on doit comparer la prochaine paire de lettres et répéter le +procédé. Par exemple, si l'on voulait comparer lexicographiquement les mots "ox" et "on", on pourrait commencer par comparer la première lettre de chaque mot, voir @@ -2100,7 +2115,7 @@ Il est important de noter que dans l'instance de `Monoid` pour `Ordering`, x \`mappend\` y n'est pas égal à y \`mappend\` x. Puisque le premier paramètre est conservé à moins d'être `EQ`, LT \`mappend\` GT retourne `LT`, alors que GT \`mappend\` LT -retournera `GT` : +retournera `GT`  > ghci> LT `mappend` GT > LT @@ -2115,7 +2130,7 @@ OK, comment est-ce que ce monoïde est utile ? Mettons que vous soiyez en train d'écrire une fonction qui prend deux chaînes de caractères, compare leur longueur, et retourne un `Ordering`. Mais si les chaînes sont de même longueur, au lieu de retourner `EQ` immédiatement, vous voulez les comparer -lexicographiquement. Une façon de faire serait : +lexicographiquement. Une façon de faire serait  > lengthCompare :: String -> String -> Ordering > lengthCompare x y = let a = length x `compare` length y @@ -2127,7 +2142,7 @@ comparaison lexicographique, et si les longueurs s'avèrent égales, on retourne l'ordre lexicographique. Mais, en mettant à profit notre compréhension d'`Ordering` comme un monoïde, on -peut réécrire cette fonction d'une manière bien plus simple : +peut réécrire cette fonction d'une manière bien plus simple  > import Data.Monoid > @@ -2135,7 +2150,7 @@ peut réécrire cette fonction d'une manière bien plus simple : > lengthCompare x y = (length x `compare` length y) `mappend` > (x `compare` y) -On peut essayer : +On peut essayer  > ghci> lengthCompare "zen" "ants" > LT @@ -2147,7 +2162,7 @@ gardé à moins d'être `EQ`, auquel cas celui de droite est gardé. C'est pourq on a placé la comparaison qu'on considère comme le critère le plus important en premier paramètre. Si l'on voulait étendre la fonction pour comparer le nombre de voyelles comme deuxième critère en importance, on pourrait modifier la -fonction ainsi : +fonction ainsi  > import Data.Monoid > @@ -2189,7 +2204,7 @@ paramètre de type `a` est un monoïde également, et d'implémenter `mappend` d façon à ce qu'il utilise l'opération `mappend` des valeurs enveloppées dans le `Just`. `Nothing` est l'élément neutre, et ainsi si l'une des deux valeurs qu'on `mappend` est `Nothing`, on conserve l'autre valeur. Voici la déclaration -d'instance : +d'instance  > instance Monoid a => Monoid (Maybe a) where > mempty = Nothing @@ -2222,13 +2237,13 @@ fait que le contenu soit un monoïde que lorsque `mappend` est appliqué à deux valeurs `Just`. Mais si l'on ne sait pas si le contenu est un monoïde, on ne peut pas faire `mappend` sur ces valeurs, donc que faire ? Eh bien, on peut par exemple toujours jeter la deuxième valeur et garder la première. Pour cela, le -type `First a` existe, et voici sa définition : +type `First a` existe, et voici sa définition  > newtype First a = First { getFirst :: Maybe a } > deriving (Eq, Ord, Read, Show) On prend un `Maybe a` et on l'enveloppe avec un *newtype*. L'instance de -`Monoid` est comme suit : +`Monoid` est comme suit  > instance Monoid (First a) where > mempty = First Nothing @@ -2248,7 +2263,8 @@ qu'il soit un `Just` ou un `Nothing`. > Just 'a' `First` est utile quand on a plein de valeurs `Maybe` et qu'on souhaite juste -savoir s'il y a un `Just` dans le tas. La fonction `mconcat` s'avère utile : +savoir s'il y a un `Just` dans le tas. La fonction `mconcat` s'avère +utile  > ghci> getFirst . mconcat . map First $ [Nothing, Just 9, Just 10] > Just 9 @@ -2257,7 +2273,7 @@ Si l'on veut un monoïde sur `Maybe a` de manière à ce que le deuxième param soit gardé lorsque les deux paramètres de `mappend` sont des valeurs `Just`, `Data.Monoid` fournit un type `Last a`, qui fonctionne comme `First a`, à l'exception que c'est la dernière valeur différente de `Nothing` qui est gardée -par `mappend` et `mconcat` : +par `mappend` et `mconcat`  > ghci> getLast . mconcat . map Last $ [Nothing, Just 9, Just 10] > Just 10 @@ -2278,7 +2294,7 @@ plis, la classe de types `Foldable` a été introduite. Comme `Functor` est pour les choses sur lequelles on peut mapper, `Foldable` est pour les choses qui peuvent être pliées ! Elle est trouvable dans `Data.Foldable` et puisqu'elle exporte des fonctions dont les noms sont en collision avec ceux du `Prelude`, -elle est préférablement importée qualifiée (et servie avec du basilic) : +elle est préférablement importée qualifiée (et servie avec du basilic)  > import qualified Foldable as F @@ -2286,7 +2302,7 @@ Pour nous éviter de précieuses frappes de clavier, on l'importe qualifiée par `F`. Bien, donc quelles sont les fonctions que cette classe de types définit ? Eh bien, parmi celles-ci sont `foldr`, `foldl`, `foldr1`, `foldl1`. Hein ? Mais on connaît déjà ces fonctions, quoi de neuf ? Comparons le type de `foldr` dans -`Foldable` avec celui de `foldr` du `Prelude` pour voir ce qui diffère : +`Foldable` avec celui de `foldr` du `Prelude` pour voir ce qui diffère  > ghci> :t foldr > foldr :: (a -> b -> b) -> b -> [a] -> b @@ -2296,7 +2312,7 @@ on connaît déjà ces fonctions, quoi de neuf ? Comparons le type de `foldr` da Ah ! Donc, alors que `foldr` prend une liste et la plie, le `foldr` de `Data.Foldable` accepte tout type qui peut être plié, pas seulement les listes ! Comme on peut s'y attendre, les deux fonctions font la même chose sur les -listes : +listes  > ghci> foldr (*) 1 [1,2,3] > 6 @@ -2319,7 +2335,7 @@ une structure de données un peu plus complexe. Vous rappelez-vous la structure de données arborescente du chapitre [Créer nos propres types et classes de types](creer-nos-propres-types-et-classes-de-types#structures-de-donnees-recursives) -? On l'avait définie ainsi : +? On l'avait définie ainsi  > data Tree a = Empty | Node a (Tree a) (Tree a) deriving (Show, Read, Eq) @@ -2331,7 +2347,7 @@ de `Foldable` afin de pouvoir le plier. Une façon de faire d'un constructeur de types une instance de `Foldable` consiste à implémenter directement `foldr`. Une autre façon, souvent bien plus simple, consiste à implémenter la fonction `foldMap`, qui fait aussi partie de la classe de types `Foldable`. `foldMap` a -pour type : +pour type  > foldMap :: (Monoid m, Foldable t) => (a -> m) -> t a -> m @@ -2347,7 +2363,7 @@ qu'il suffit simplement d'implémenter cette fonction pour que notre type soit une instance de `Foldable`. Ainsi, si l'on implémente `foldMap` pour un type, on obtient `foldr` et `foldl` sans effort ! -Voici comment un `Tree` est fait instance de `Foldable` : +Voici comment un `Tree` est fait instance de `Foldable`  > instance F.Foldable Tree where > foldMap f Empty = mempty @@ -2357,9 +2373,9 @@ Voici comment un `Tree` est fait instance de `Foldable` : accordéon -On pense ainsi : si l'on nous donne une fonction qui prend un élément de notre -arbre et retourne une valeur monoïdale, comment réduit-on notre arbre à une -simple valeur monoïdale ? Quand nous faisions `fmap` sur notre arbre, on +On pense ainsi  si l'on nous donne une fonction qui prend un élément de +notre arbre et retourne une valeur monoïdale, comment réduit-on notre arbre à +une simple valeur monoïdale ? Quand nous faisions `fmap` sur notre arbre, on appliquait la fonction mappée au nœud, puis on mappait récursivement la fonction sur le sous-arbre gauche et sur le sous-arbre droit. Ici, on nous demande non seulement de mapper la fonction, mais également de joindre les @@ -2384,8 +2400,8 @@ retourne une valeur monoïdale. On la reçoit en paramètre de la fonction `foldMap` et tout ce qu'on a besoin de décider est où l'appliquer et comment joindre les monoïdes qui en résultent. -Maintenant qu'on a une instance de `Foldable` pour notre type arborescent, on -a `foldr` et `foldl` gratuitement ! Considérez cet arbre : +Maintenant qu'on a une instance de `Foldable` pour notre type arborescent, on a +`foldr` et `foldl` gratuitement ! Considérez cet arbre  > testTree = Node 5 > (Node 3 @@ -2400,7 +2416,7 @@ a `foldr` et `foldl` gratuitement ! Considérez cet arbre : Il a `5` pour racine, puis son nœud gauche contient `3` avec `1` à sa gauche et `6` à sa droite. Le nœud droit de la racine contient `9` avec un `8` à sa gauche et un `10` tout à droite. Avec une instance de `Foldable`, on peut faire -tous les plis qu'on fait sur des listes : +tous les plis qu'on fait sur des listes  > ghci> F.foldl (+) 0 testTree > 42 @@ -2410,7 +2426,7 @@ tous les plis qu'on fait sur des listes : `foldMap` ne sert pas qu'à créer les instances de `Foldable`, elle sert également à réduire une structure à une simple valeur monoïdale. Par exemple, si l'on voulait savoir si n'importe quel nombre de notre arbre est égal à `3`, -on pourrait faire : +on pourrait faire  > ghci> getAny $ F.foldMap (\x -> Any $ x == 3) testTree > True @@ -2418,7 +2434,7 @@ on pourrait faire : Ici, `\x -> Any $ x == 3` est une fonction qui prend un nombre et retourne une valeur monoïdale, dans ce cas un `Bool` enveloppé en `Any`. `foldMap` applique la fonction à chaque élément de l'arbre, puis réduit les monoïdes résultants en -une unique valeur monoïdale à l'aide de `mappend`. Si l'on faisait : +une unique valeur monoïdale à l'aide de `mappend`. Si l'on faisait  > ghci> getAny $ F.foldMap (\x -> Any $ x > 15) testTree > False @@ -2433,7 +2449,7 @@ On peut aussi facilement transformer notre arbre en une liste en faisant `foldMap` avec la fonction `\x -> []`. En projetant d'abord la fonction sur l'arbre, chaque élément devient une liste singleton. Puis, `mappend` a lieu entre tous ces singletons et retourne une unique liste qui contient tous les -éléments de notre arbre : +éléments de notre arbre  > ghci> F.foldMap (\x -> [x]) testTree > [1,3,6,5,8,9,10] @@ -2444,13 +2460,19 @@ aux arbres, elles fonctionnent pour toute instance de `Foldable`. diff --git a/fonctions-d-ordre-superieur.mkd b/fonctions-d-ordre-superieur.mkd index 3358750..86bfc9b 100644 --- a/fonctions-d-ordre-superieur.mkd +++ b/fonctions-d-ordre-superieur.mkd @@ -3,13 +3,17 @@
@@ -25,7 +29,9 @@ que des étapes qui changent un état et bouclent. Elles sont un moyen très puissant de résoudre des problèmes et de réflexion sur les programmes.

+ Fonctions curryfiées +

En Haskell, toutes les fonctions ne prennent en fait qu'un unique paramètre. @@ -38,7 +44,7 @@ de retourner le plus grand des deux. Faire `max 4 5` crée une fonction qui prend un paramètre, et retourne soit `4`, soit ce paramètre. On applique alors cette fonction à la valeur `5` pour produire le résultat attendu. On ne dirait pas comme ça, mais c'est en fait assez cool. Les deux appels suivants sont -ainsi équivalents : +ainsi équivalents : > ghci> max 4 5 > 5 @@ -50,10 +56,10 @@ ainsi équivalents : Mettre un espace entre deux choses consiste à **appliquer une fonction**. L'espace est en quelque sorte un opérateur, et il a la plus grande précédence. Examinons le type de `max`. C'est `max :: (Ord a) => a -> a -> a`. On peut -aussi écrire ceci : `max :: (Ord a) => a -> (a -> a)`. Et le lire ainsi : `max` -prend un `a` et retourne (c'est le premier `->`)) une fonction qui prend un `a` -et retourne un `a`. C'est pourquoi le type de retour et les paramètres d'une -fonction sont séparés par des flèches. +aussi écrire ceci : `max :: (Ord a) => a -> (a -> a)`. Et le lire +ainsi : `max` prend un `a` et retourne (c'est le premier `->`)) une +fonction qui prend un `a` et retourne un `a`. C'est pourquoi le type de retour +et les paramètres d'une fonction sont séparés par des flèches. En quoi cela nous aide-t-il ? Pour faire simple, si on appelle une fonction avec trop peu de paramètres, on obtient une fonction **appliquée @@ -63,7 +69,7 @@ peu de paramètres) est un moyen grâcieux de créer des fonctions à la volée de les passer à d'autres fonctions pour qu'elle les complète avec d'autres données. -Regardons cette fonction violemment simple : +Regardons cette fonction violemment simple : > multThree :: (Num a) => a -> a -> a -> a > multThree x y z = x * y * z @@ -80,7 +86,7 @@ après est son unique type retourné. Donc, notre fonction prend un `a` et retourne une fonction qui a pour type `(Num a) => a -> (a -> a)`. De façon similaire, cette fonction prend un `a` et retourne une fonction qui a pour type `(Num a) => a -> a`. Et cette fonction, prend un `a` et retourne un `a`. -Regardez ça : +Regardez ça : > ghci> let multTwoWithNine = multThree 9 > ghci> multTwoWithNine 2 3 @@ -91,7 +97,7 @@ Regardez ça : En appelant des fonctions avec trop peu de paramètres, en quelque sorte, on crée de nouvelles fonctions à la volée. Comment créer une fonction qui prend un -nombre, et le compare à `100` ? Comme ça : +nombre, et le compare à `100` ? Comme ça : > compareWithHundred :: (Num a, Ord a) => a -> Ordering > compareWithHundred x = compare 100 x @@ -100,7 +106,7 @@ Si on l'appelle avec `99`, elle retourne `GT`. Simple. Remarquez-vous le `x` tout à droite des deux côtés de l'équation ? Réfléchissons à présent à ce que `compare 100` retourne. Cela retourne une fonction qui prend un nombre, et le compare à 100. Ouah ! Est-ce que ce n'était pas la fonction qu'on voulait ? On -devrait réécrire cela : +devrait réécrire cela : > reWithHundred :: (Num a, Ord a) => a -> Ordering > compareWithHundred = compare 100 @@ -122,14 +128,14 @@ Les fonctions infixes peuvent aussi être appliquées partiellement à l'aide de sections. Sectionner une fonction infixe revient à l'entourer de parenthèses et à lui fournir un paramètre sur l'un de ses côtés. Cela crée une fonction qui attend l'autre paramètre et l'applique du côté où il lui manquait une opérande. -Une fonction insultante de trivialité : +Une fonction insultante de trivialité : > divideByTen :: (Floating a) => a -> a > divideByTen = (/10) Appeler, disons, `divideByTen 200` est équivalent à faire `200 / 10`, ce qui est aussi équivalent à `(/10) 200`. Maintenant, une fonction qui vérifie si un -caractère est en majuscule : +caractère est en majuscule : > isUpperAlphanum :: Char -> Bool > isUpperAlphanum = (`elem` ['A'..'Z']) @@ -138,7 +144,7 @@ La seule chose spéciale à propos des sections, c'est avec `-`. Par définition des sections, (-4) devrait être la fonction qui attend un nombre, et lui soustrait 4. Cependant, par habitude, `(-4)` signifie la valeur moins quatre. Pour créer une fonction qui soustrait 4 du nombre en paramètre, appliquez -plutôt partiellement la fonction `subtract` ainsi : `(subtract 4)`. +plutôt partiellement la fonction `subtract` ainsi : `(subtract 4)`. Que se passe-t-il si on tape `multThree 3 4` directement dans GHCi sans la lier avec un *let* ou la passer à une autre fonction ? @@ -159,7 +165,9 @@ puis appelle `show` sur `2` pour obtenir une représentation textuelle de ce nombre. La représentation de `2` est `"2"`, et c'est ce qu'il nous affiche.

-À l'ordre du jour : de l'ordre supérieur + +À l'ordre du jour : de l'ordre supérieur +

Les fonctions peuvent prendre des fonctions en paramètres et retourner des @@ -185,10 +193,10 @@ soit. Mais alors, le second paramètre doit être du type correspondant.
-**Note :** à partir de maintenant, nous dirons qu'une fonction prend plusieurs -paramètres malgré le fait qu'elle ne prend en réalité qu'un paramètre et -retourne une fonction appliquée partiellement jusqu'à finalement arriver à une -valeur solide. Donc, pour simplifier, on dira que `a -> a -> a` prend deux +**Note :** à partir de maintenant, nous dirons qu'une fonction prend +plusieurs paramètres malgré le fait qu'elle ne prend en réalité qu'un paramètre +et retourne une fonction appliquée partiellement jusqu'à finalement arriver à +une valeur solide. Donc, pour simplifier, on dira que `a -> a -> a` prend deux paramètres, bien que l'on sache ce qui se trame en réalité sous la couverture.
@@ -196,7 +204,7 @@ paramètres, bien que l'on sache ce qui se trame en réalité sous la couverture Le corps de la fonction est plutôt simple. On utilise le paramètre `f` comme une fonction, on applique cette fonction à `x` en les séparant par un espace, puis on applique `f` au résultat une nouvelle fois. Jouons un peu avec la -fonction : +fonction : > ghci> applyTwice (+3) 10 > 16 @@ -218,7 +226,7 @@ Maintenant, nous allons profiter de la programmation d'ordre supérieur pour implémenter une fonction très utile de la bibliothèque standard. Elle s'appelle `zipWith`. Elle prend une fonction, et deux listes, et zippe les deux listes à l'aide de la fonction, en l'appliquant aux éléments correspondants. Voici une -implémentation : +implémentation : > zipWith' :: (a -> b -> c) -> [a] -> [b] -> [c] > zipWith' _ [] _ = [] @@ -245,7 +253,7 @@ corps de la fonction pour le dernier motif est également similaire à `zip`, seulement à la place de faire `(x, y)`, elle fait `f x y`. Une même fonction d'ordre supérieur peut être utilisée pour une multitude de différentes tâches si elle est assez générale. Voici une petite démonstration de toutes les -différentes choses que `zipWith'` peut faire : +différentes choses que `zipWith'` peut faire : > ghci> zipWith' (+) [4,2,5,6] [2,6,2,3] > [6,8,7,9] @@ -271,7 +279,7 @@ de solutions et éliminer celles qui ne vous intéressent pas. Nous allons encore implémenter une fonction de la bibliothèque standard, `flip`. Elle prend une fonction et retourne une fonction qui est comme la fonction initiale, mais dont les deux premiers arguments ont échangé leur -place. On peut l'implémenter ainsi : +place. On peut l'implémenter ainsi : > flip' :: (a -> b -> c) -> (b -> a -> c) > flip' f = g @@ -304,7 +312,9 @@ d'ordre supérieur en réfléchissant à ce que leur résultat serait si elles > [5,4,3,2,1]

+ Maps et filtres +

`map` prend une fonction et une liste, et applique la fonction à tous les @@ -320,7 +330,7 @@ et retourne une liste de `b`. Il est intéressant de voir que rien qu'en regardant la signature d'une fonction, vous pouvez parfois dire exactement ce qu'elle fait. `map` est une de ces fonctions d'ordre supérieur extrèmement utile qui peut être utilisée de milliers de façons différentes. La voici en -action : +action : > ghci> map (+3) [1,5,3,1,6] > [4,8,6,4,9] @@ -343,7 +353,7 @@ crochets s'entassent de manière déplaisante. `filter` est une fonction qui prend un prédicat (un prédicat est une fonction qui dit si quelque chose est vrai ou faux, une fonction qui retourne une valeur booléenne) et une liste, et retourne la liste des éléments qui satisfont le -prédicat. La signature et l'implémentation : +prédicat. La signature et l'implémentation : > filter :: (a -> Bool) -> [a] -> [a] > filter _ [] = [] @@ -352,7 +362,7 @@ prédicat. La signature et l'implémentation : > | otherwise = filter p xs Plutôt simple. Si `p x` vaut `True`, l'élément est inclus dans la nouvelle -liste. Sinon, il reste dehors. Quelques exemples d'utilisation : +liste. Sinon, il reste dehors. Quelques exemples d'utilisation : > ghci> filter (>3) [1,5,3,2,1,6,4,3,2,1] > [5,6,4] @@ -378,7 +388,7 @@ des prédicats à l'aide de `&&`. Vous souvenez-vous de la fonction quicksort du [chapitre précédent](recursivite) ? Nous avions utilisé des listes en compréhension pour filtrer les éléments plus petits que le pivot. On peut faire de même avec -`filter` : +`filter` : > quicksort :: (Ord a) => [a] -> [a] > quicksort [] = [] @@ -432,7 +442,7 @@ mille. D'abord, on va commencer par mapper `(^2)` sur la liste infinie `[1..]`. Ensuite, on va filtrer pour garder les nombres impairs. Puis, on prendra des éléments de cette liste tant qu'ils restent inférieurs à 10 000. Enfin, on va sommer cette liste. On n'a même pas besoin d'une fonction pour ça, -une ligne dans GHCi suffit : +une ligne dans GHCi suffit : > ghci> sum (takeWhile (<10000) (filter odd (map (^2) [1..]))) > 166650 @@ -440,7 +450,7 @@ une ligne dans GHCi suffit : Génial ! On commence avec une donnée initiale (la liste infinie de tous les entiers naturels) et on mappe par dessus, puis on filtre et on coupe jusqu'à avoir ce que l'on désire, et on somme le tout. On aurait pu écrire ceci à -l'aide de listes en compréhension : +l'aide de listes en compréhension : > ghci> sum (takeWhile (<10000) [n^2 | n <- [1..], odd (n^2)]) > 166650 @@ -459,13 +469,13 @@ prend un entier naturel. Si cet entier est pair, on le divise par deux. S'il est impair, on le multiplie par 3 puis on ajoute 1. On prend ce nombre, et on recommence, ce qui produit un nouveau nombre, et ainsi de suite. On obtient donc une chaîne de nombres. On pense que, quel que soit le nombre de départ, la -chaîne termine, avec pour valeur 1. Si on prend 13, on obtient la suite : *13, -40, 20, 10, 5, 16, 8, 4, 2, 1*. 13 fois 3 plus 1 vaut 40. 40 divisé par 2 vaut -20, etc. On voit que la chaîne contient 10 termes. +chaîne termine, avec pour valeur 1. Si on prend 13, on obtient la suite : +*13, 40, 20, 10, 5, 16, 8, 4, 2, 1*. 13 fois 3 plus 1 vaut 40. 40 divisé par 2 +vaut 20, etc. On voit que la chaîne contient 10 termes. -Maintenant on cherche à savoir : **pour tout nombre de départ compris entre 1 -et 100, combien de chaînes ont une longueur supérieure à 15 ?** D'abord, on va -écrire une fonction qui produit ces chaînes : +Maintenant on cherche à savoir : **pour tout nombre de départ compris +entre 1 et 100, combien de chaînes ont une longueur supérieure à 15 ?** +D'abord, on va écrire une fonction qui produit ces chaînes : > chain :: (Integral a) => a -> [a] > chain 1 = [1] @@ -484,7 +494,7 @@ récursive assez simple. > [30,15,46,23,70,35,106,53,160,80,40,20,10,5,16,8,4,2,1] Yay ! Ça a l'air de marcher correctement. À présent, la fonction qui nous donne -notre réponse : +notre réponse : > ngChains :: Int > numLongChains = length (filter isLong (map chain [1..100])) @@ -498,10 +508,10 @@ de chaînes dans la liste finale.
-**Note :** Cette fonction a pour type `numLongChains :: Int` parce que `length` -a pour type `Int` au lieu de `Num a`, pour des raisons historiques. Si nous -voulions retourner `Num a`, on aurait pu utiliser `fromIntegral` sur le nombre -retourné. +**Note :** Cette fonction a pour type `numLongChains :: Int` parce que +`length` a pour type `Int` au lieu de `Num a`, pour des raisons historiques. Si +nous voulions retourner `Num a`, on aurait pu utiliser `fromIntegral` sur le +nombre retourné.
@@ -529,7 +539,9 @@ Demander l'élément d'index `4` de notre liste retourne une fonction équivalen 5` ou juste `4 * 5`.

+ Lambdas +

lambda @@ -546,7 +558,7 @@ possible vers la droite. Si vous regardez 10cm plus haut, vous verrez qu'on a utilisé une liaison *where* dans `numLongChains` pour créer la fonction `isLong`, avec pour seul but de passer cette fonction à `filter`. Au lieu de faire ça, on pourrait -utiliser une lambda : +utiliser une lambda : > numLongChains :: Int > numLongChains = length (filter (\xs -> length xs > 15) (map chain [1..100])) @@ -582,8 +594,8 @@ attention en filtrant dans les lambdas. Les lambdas sont habituellement entourées de parenthèses à moins qu'on veuille qu'elles s'étendent le plus possible à droite. Voilà quelque chose -d'intéressant : vu la façon dont sont curryfiées les fonctions par défaut, ces -deux codes sont équivalents : +d'intéressant : vu la façon dont sont curryfiées les fonctions par défaut, +ces deux codes sont équivalents : > addThree :: (Num a) => a -> a -> a -> a > addThree x y z = x + y + z @@ -597,7 +609,7 @@ sûr, la première façon d'écrire est bien plus lisible, la seconde ne sert qu illustrer la curryfication. Cependant, cette notation s'avère parfois pratique. Je trouve la fonction -`flip` plus lisible définie ainsi : +`flip` plus lisible définie ainsi : > flip' :: (a -> b -> c) -> b -> a -> c > flip' f = \x y -> f y x @@ -611,7 +623,9 @@ fait que cette fonction est généralement appliquée partiellement avant d'êtr passée à une autre comme paramètre.

+ Plie mais ne rompt pas +

origami @@ -621,7 +635,7 @@ récurrent dans nos fonctions récursives qui opéraient sur des listes. Nous introduisions un motif `x:xs` et nous faisions quelque chose avec la tête et quelque chose avec la queue. Il s'avère que c'est un motif très commun, donc il existe quelques fonctions très utiles qui l'encapsulent. Ces fonctions sont -appelées des *folds* (NDT : des plis). Elles sont un peu comme la fonction +appelées des *folds* (NDT : des plis). Elles sont un peu comme la fonction `map`, seulement qu'elles réduisent une liste à une simple valeur. Un *fold* prend une fonction binaire, une valeur de départ (que j'aime appeler @@ -645,7 +659,7 @@ qu'une récursivité explicite. > sum' :: (Num a) => [a] -> a > sum' xs = foldl (\acc x -> acc + x) 0 xs -Un, deux, trois, test : +Un, deux, trois, test : > ghci> sum' [3,5,2,1] > 11 @@ -664,9 +678,9 @@ produit `11`. Bravo, vous venez d'achever votre premier pli ! Le diagramme professionnel sur la gauche illustre la façon dont le pli se déroule, étape par étape (jour après jour !). Le nombre vert kaki est l'accumulateur. Vous pouvez voir comme la liste est consommée par la gauche par -l'accumulateur. Om nom nom nom ! (NDT : "Miam miam miam !") Si on prend en +l'accumulateur. Om nom nom nom ! (NDT : "Miam miam miam !") Si on prend en compte le fait que les fonctions sont curryfiées, on peut écrire cette -implémentation encore plus rapidement : +implémentation encore plus rapidement : > sum' :: (Num a) => [a] -> a > sum' = foldl (+) 0 @@ -751,7 +765,7 @@ Les fonctions `foldl1` et `foldr1` fonctionnent quasiment comme `foldl` et `foldr`, mais n'ont pas besoin d'une valeur de départ. Elles considèrent que le premier (ou le dernier respectivement) élément de la liste est la valeur de départ, et commencent à plier à partir de l'élément suivant. Avec cela en tête, -la fonction `sum` peut être implémentée : `sum = foldl1 (+)`. Puisqu'elles +la fonction `sum` peut être implémentée : `sum = foldl1 (+)`. Puisqu'elles dépendent du fait que les listes qu'elles essaient de plier aient au moins un élément, elles peuvent provoquer des erreurs à l'exécution si on les appelle sur des listes vides. `foldl` et `foldr`, d'un autre côté, fonctionnent bien @@ -761,7 +775,7 @@ vide, vous pouvez probablement utiliser `foldl1` et `foldr1` pour l'implémenter. Histoire de vous montrer la puissance des plis, on va implémenter un paquet de -fonctions de la bibliothèque standard avec eux : +fonctions de la bibliothèque standard avec eux : > maximum' :: (Ord a) => [a] -> a > maximum' = foldr1 (\x acc -> if x > acc then x else acc) @@ -791,18 +805,18 @@ paramètres dans l'autre sens. C'est pourquoi, on aurait pu écrire reverse comm `foldl (flip (:)) []`. Une autre façon de se représenter les plis à droite et à gauche consiste à se -dire : mettons qu'on a un pli à droite et une fonction binaire `f`, et une +dire : mettons qu'on a un pli à droite et une fonction binaire `f`, et une valeur initiale `z`. Si l'on plie à droite la liste `[3, 4, 5, 6]`, on fait en -gros cela : `f 3 (f 4 (f 5 (f 6 z)))`. `f` est appelé avec le dernier élément -de la liste et l'accumulateur, cette valeur est donnée comme accumulateur à la -prochaine valeur, etc. Si on prend pour `f` la fonction `+` et pour -accumulateur de départ `0`, ça donne `3 + (4 + (5 + (6 + 0)))`. Ou, avec un `+` -préfixe, `(+) 3 ((+) 4 ((+) 5 ((+) 6 0)))`. De façon similaire, un pli à gauche -avec la fonction binaire `g` et l'accumulateur `z` est équivalent p `g (g (g (g -z 3) 4) 5) 6`. Si on utilise `flip (:)` comme fonction binaire, et `[]` comme -accumulateur (de manière à renverser la liste), c'est équivalent à `flip (:) -(flip (:) (flip (:) (flip (:) [] 3) 4) 5) 6`. Et pour sûr, évaluer cette -expression renvoie `[6, 5, 4, 3]`. +gros cela : `f 3 (f 4 (f 5 (f 6 z)))`. `f` est appelé avec le dernier +élément de la liste et l'accumulateur, cette valeur est donnée comme +accumulateur à la prochaine valeur, etc. Si on prend pour `f` la fonction `+` +et pour accumulateur de départ `0`, ça donne `3 + (4 + (5 + (6 + 0)))`. Ou, +avec un `+` préfixe, `(+) 3 ((+) 4 ((+) 5 ((+) 6 0)))`. De façon similaire, un +pli à gauche avec la fonction binaire `g` et l'accumulateur `z` est équivalent +p `g (g (g (g z 3) 4) 5) 6`. Si on utilise `flip (:)` comme fonction binaire, +et `[]` comme accumulateur (de manière à renverser la liste), c'est équivalent +à `flip (:) (flip (:) (flip (:) (flip (:) [] 3) 4) 5) 6`. Et pour sûr, évaluer +cette expression renvoie `[6, 5, 4, 3]`. `scanl` et `scanr` sont comme `foldl` et `foldr`, mais rapportent tous les résultats intermédiaires de l'accumulateur sous forme d'une liste. Il existe @@ -821,16 +835,16 @@ En utilisant un `scanl`, le résultat final sera dans le dernier élément de la liste retournée, alors que pour un `scanr`, il sera en tête. Les scans sont utilisés pour surveiller la progression d'une fonction -implémentable comme un pli. Répondons à cette question : **Combien d'entiers -naturels croissants faut-il pour que la somme de leurs racines carrées dépasse -100 ?** Pour récupérer les racines carrées de tous les entiers naturels, on -fait `map sqrt [1..]`. Maintenant, pour obtenir leur somme, on pourrait faire -un pli, mais puisqu'on s'intéresse à la progression de la somme, on va plutôt -faire un scan. Une fois le scan fait, on compte juste le nombre de sommes qui -sont inférieures à 1000. La première somme sera normalement égale à 1. La -deuxième, à 1 plus racine de 2. La troisième à cela plus racine de 3. Si X de -ces sommes sont inférieures à 1000, alors il faut X+1 éléments pour dépasser -1000. +implémentable comme un pli. Répondons à cette question : **Combien +d'entiers naturels croissants faut-il pour que la somme de leurs racines +carrées dépasse 100 ?** Pour récupérer les racines carrées de tous les entiers +naturels, on fait `map sqrt [1..]`. Maintenant, pour obtenir leur somme, on +pourrait faire un pli, mais puisqu'on s'intéresse à la progression de la somme, +on va plutôt faire un scan. Une fois le scan fait, on compte juste le nombre de +sommes qui sont inférieures à 1000. La première somme sera normalement égale à +1. La deuxième, à 1 plus racine de 2. La troisième à cela plus racine de 3. Si +X de ces sommes sont inférieures à 1000, alors il faut X+1 éléments pour +dépasser 1000. > sqrtSums :: Int > sqrtSums = length (takeWhile (<1000) (scanl1 (+) (map sqrt [1..]))) + 1 @@ -848,11 +862,13 @@ croissante, `filter` ne le sait pas, donc on utilise `takeWhile` pour arrêter de scanner dès qu'une somme est plus grande que 1000.

+ Appliquer des fonctions avec $ +

Bien, maintenant, découvrons la fonction `$`, aussi appelée *application de -fonction*. Voyons sa définition : +fonction*. Voyons sa définition : > ($) :: (a -> b) -> a -> b > f $ x = f x @@ -891,17 +907,19 @@ peut par exemple mapper l'application de fonction sur une liste de fonctions. > [7.0,30.0,9.0,1.7320508075688772]

+ Composition de fonctions +

-En mathématique, la composition de fonctions est définie ainsi : ![composition -de fonctions](img/composition.png), ce qui signifie que deux fonctions -produisent une nouvelle fonction qui, lorsqu'elle est appelée avec un +En mathématique, la composition de fonctions est définie ainsi : +![composition de fonctions](img/composition.png), ce qui signifie que deux +fonctions produisent une nouvelle fonction qui, lorsqu'elle est appelée avec un paramètre, disons x, est équivalent à l'appel de g sur x, puis l'appel de f sur le résultat. En Haskell, la composition de fonction est plutôt identique. On utilise la -fonction `.`, définie ainsi : +fonction `.`, définie ainsi : > (.) :: (b -> c) -> (a -> b) -> a -> c > f . g = \x -> f (g x) @@ -920,25 +938,25 @@ volée pour les passer à d'autre fonctions. Bien sûr, on peut utiliser des lambdas à cet effet, mais souvent, la composition de fonctions est plus claire et concise. Disons qu'on a une liste de nombres, et qu'on veut tous les rendre négatifs. Un moyen de procéder serait de prendre la valeur absolue de chacun -d'entre eux, et de renvoyer son opposé : +d'entre eux, et de renvoyer son opposé : > ghci> map (\x -> negate (abs x)) [5,-3,-6,7,-3,2,-19,24] > [-5,-3,-6,-7,-3,-2,-19,-24] Remarquez la lambda-expression, comme elle ressemble à une composition de -fonctions. Avec la composition, on peut écrire : +fonctions. Avec la composition, on peut écrire : > ghci> map (negate . abs) [5,-3,-6,7,-3,2,-19,24] > [-5,-3,-6,-7,-3,-2,-19,-24] Fabuleux ! La composition est associative à droite, donc on peut composer -plusieurs fonctions à la fois. L'expression `f (g (z x))` est équivalente à -`(f . g . z) x`. Avec ça en tête, on peut transformer : +plusieurs fonctions à la fois. L'expression `f (g (z x))` est équivalente à `(f +. g . z) x`. Avec ça en tête, on peut transformer : > ghci> map (\xs -> negate (sum (tail xs))) [[1..5],[3..6],[1..7]] > [-14,-15,-27] -en : +en : > ghci> map (negate . sum . tail) [[1..5],[3..6],[1..7]] > [-14,-15,-27] @@ -947,12 +965,12 @@ Mais qu'en est-il des fonctions à plusieurs paramètres ? Pour les utiliser dan de la composition de fonctions, il faut les avoir partiellement appliquées jusqu'à ce qu'elles ne prennent plus qu'un paramètre. `sum (replicate 5 (max 6.7 8.9))` peut être réécrit `(sum . replicate 5 . max 6.7) 8.9` ou bien `sum . -replicate 5 . max 6.7 $ 8.9`. Ce qui se passe ici : une fonction qui prend la -même chose que `max 6.7` et applique `replicate 5` à celle-ci est créée. +replicate 5 . max 6.7 $ 8.9`. Ce qui se passe ici : une fonction qui prend +la même chose que `max 6.7` et applique `replicate 5` à celle-ci est créée. Ensuite, une fonction qui prend ça et applique `sum` est créée. Finalement, -cette fonction est appelée avec `8.9`. Vous liriez normalement comme cela : -prend `8.9` et applique `max 6.7`, puis applique `replicate 5` à ça, et -applique `sum` à ça. Si vous voulez réécrire une expression pleine de +cette fonction est appelée avec `8.9`. Vous liriez normalement comme +cela : prend `8.9` et applique `max 6.7`, puis applique `replicate 5` à +ça, et applique `sum` à ça. Si vous voulez réécrire une expression pleine de parenthèses avec de la composition de fonctions, vous pouvez commencer par mettre le dernier paramètre de la dernière fonction à la suite d'un `$`, et ensuite composer les appels successifs, en les écrivant sans leur dernier @@ -964,7 +982,7 @@ réécriture contiendra probablement trois opérateurs de composition. Une autre utilisation courante de la composition de fonctions consiste à définir des fonctions avec un style dit sans point (certains disent même sans -but !). Prenez par exemple la fonction définie auparavant : +but !). Prenez par exemple la fonction définie auparavant : > sum' :: (Num a) => [a] -> a > sum' xs = foldl (+) 0 xs @@ -979,7 +997,7 @@ est dit dans le style sans point. Comment écrire ceci en style sans point ? On ne peut pas se débarasser du `x` des deux côtés. Le `x` du côté du corps de la fonction a des parenthèses après lui. `cos (max 50)` ne voudrait rien dire. On ne peut pas prendre le cosinus d'une fonction. On peut cependant exprimer -`fn` comme une composition de fonctions : +`fn` comme une composition de fonctions : > fn = ceiling . negate . tan . cos . max 50 @@ -1003,13 +1021,13 @@ la forme d'une fonction. > oddSquareSum :: Integer > oddSquareSum = sum (takeWhile (<10000) (filter odd (map (^2) [1..]))) -Étant un fan de composition, j'aurais probablement écrit : +Étant un fan de composition, j'aurais probablement écrit : > oddSquareSum :: Integer > oddSquareSum = sum . takeWhile (<10000) . filter odd . map (^2) $ [1..] Cependant, s'il était possible que quelqu'un doive lire ce code, j'aurais -plutôt écrit : +plutôt écrit : > oddSquareSum :: Integer > oddSquareSum = @@ -1023,13 +1041,17 @@ Je ne gagnerai pas de compétition de golf ainsi, mais quelqu'un qui aura à lir
diff --git a/html/hscolour.css b/html/hscolour.css index 06a5184..ae87b66 100644 --- a/html/hscolour.css +++ b/html/hscolour.css @@ -1,9 +1,11 @@ body {background-image: url(img/bg.png); font-size: 100%; line-height: 25px; - font-family: Arial, sans-serif; margin: 0} + font-size: 14px; font-family: Arial, sans-serif; margin: 0} h1 {font-size: 60px; line-height: 60px; font-family: georgia, Arial, serif; margin: 0 0 15px 0; font-weight: normal;} h2 {clear: left;} -pre {background-color: black; padding: 4px 4px 4px 4px; clear: both;} +pre {background-color: black; padding: 10px; margin: 0px 0 25px 0 !important; + font-size: 14px; line-height: 18px; width: auto !important; + overflow-x: auto; overflow-y: auto;} code {background-color: #DDD; font-weight: bold; padding: 0 2px 0 2px; white-space: nowrap;} ol {list-style-type: decimal; font-family: arial,serif; font-size: 24px; diff --git a/introduction.mkd b/introduction.mkd index 9600a6a..6ae866d 100644 --- a/introduction.mkd +++ b/introduction.mkd @@ -9,7 +9,9 @@ [Table des matières](chapitres)
  • + Démarrons +
  • @@ -49,11 +51,12 @@ gentils, patients et compréhensifs envers les débutants. J'ai échoué dans mon apprentissage de Haskell à peu près deux fois avant d'arriver à le saisir, car cela me semblait trop bizarre et je ne comprenais pas. Mais alors, j'ai eu le déclic, et après avoir passé cet obstacle initial, -le reste est venu plutôt facilement. Ce que j'essaie de vous dire : Haskell est -génial, et si vous êtes intéressés par la programmation, vous devriez -l'apprendre même si cela semble bizarre au début. Apprendre Haskell est très -similaire à l'apprentissage de la programmation la première fois - c'est fun ! -Ca vous force à penser différemment, ce qui nous amène à la section suivante… +le reste est venu plutôt facilement. Ce que j'essaie de vous dire : +Haskell est génial, et si vous êtes intéressés par la programmation, vous +devriez l'apprendre même si cela semble bizarre au début. Apprendre Haskell est +très similaire à l'apprentissage de la programmation la première fois - c'est +fun ! Ca vous force à penser différemment, ce qui nous amène à la section +suivante…

    Donc, qu'est-ce qu'Haskell ? @@ -78,9 +81,9 @@ chose plus tard, car vous venez de dire qu'il était égal à 5. Quoi, vous ête un menteur ? Donc, dans les langages purement fonctionnels, une fonction n'a pas d'effets de bord. La seule chose qu'une fonction puisse faire, c'est calculer quelque chose et retourner le résultat. Au départ, cela semble plutôt -limitant, mais il s'avère que ça a de très intéressantes conséquences : si une -fonction est appelée deux fois, avec les mêmes paramètres, alors il est garanti -qu'elle renverra le même résultat. On appelle ceci la transparence +limitant, mais il s'avère que ça a de très intéressantes conséquences : si +une fonction est appelée deux fois, avec les mêmes paramètres, alors il est +garanti qu'elle renverra le même résultat. On appelle ceci la transparence référentielle, et cela permet non seulement au compilateur de raisonner à propos du comportement du programme, mais également à vous de déduire facilement (et même de prouver) qu'une fonction est correcte, puis de @@ -177,7 +180,9 @@ faire à présent. [Table des matières](chapitres)
  • + Démarrons +
  • diff --git a/modules.mkd b/modules.mkd index 6452877..85381a0 100644 --- a/modules.mkd +++ b/modules.mkd @@ -3,13 +3,19 @@ @@ -61,7 +67,7 @@ fonction équivalente à `\xs -> length (nub xs)`. Vous pouvez aussi importer des fonctions d'un module dans l'espace de nommage global de GHCi. Si vous êtes dans GHCi, et que vous désirez appeler des -fonctions exportées par `Data.List`, faites : +fonctions exportées par `Data.List`, faites : > ghci> :m + Data.List @@ -75,7 +81,7 @@ besoin d'utiliser `:m +` pour accéder à ces modules. Si vous n'avez besoin que de quelques fonctions d'un module, vous pouvez importer sélectivement juste ces fonctions. Si nous ne voulions que `nub` et -`sort` de `Data.List`, on ferait : +`sort` de `Data.List`, on ferait : > import Data.List (nub, sort) @@ -83,7 +89,7 @@ Vous pouvez aussi choisir d'importer toutes les fonctions d'un module, sauf certaines. C'est souvent utile quand plusieurs modules exportent des fonctions qui ont le même nom et que vous voulez vous débarasser de celles qui ne vous concernent pas. Mettons qu'on ait défini une fonction `nub` et qu'on veuille -importer toutes les fonctions de `Data.List` à part `nub` : +importer toutes les fonctions de `Data.List` à part `nub` : > import Data.List hiding (nub) @@ -92,7 +98,7 @@ qualifiés. Le module `Data.Map`, qui offre une structure de donnée pour trouve des valeurs grâce à une clé, exporte un paquet de fonctions qui ont les mêmes noms que celles du `Prelude`, comme `filter` ou `null`. Donc, quand on importe `Data.Map` et qu'on appelle `filter`, Haskell ne sait pas de laquelle on parle. -Voici comment on résout cela : +Voici comment on résout cela : > import qualified Data.Map @@ -100,7 +106,7 @@ Cela nous force à écrire `Data.Map.filter` pour désigner la fonction `filter` de `Data.Map`, alors que `filter` tout court correpond à la fonction du `Prelude` qu'on connaît et qu'on aime tous. Mais écrire `Data.Map` devant chaque fonction du module est un peu fastidieux. C'est pourquoi on peut -renommer les imports qualifiés : +renommer les imports qualifiés : > import qualified Data.Map as M @@ -133,7 +139,7 @@ manière qualifiée, il n'a de collision avec aucun nom du `Prelude`, à part Intéressons-nous à d'autres fonctions que nous n'avions pas encore vues. `intersperse` prend un élément et une liste, et place cet élément entre chaque -paire d'éléments de la liste. Démonstration : +paire d'éléments de la liste. Démonstration : > ghci> intersperse '.' "MONKEY" > "M.O.N.K.E.Y" @@ -159,8 +165,8 @@ vice versa. Mettons qu'on ait les polynômes *3x² + 5x + 9*, *10x + 9* et *8x³ + 5x² + x - 1*, et que l'on souhaite les additionner. On peut utiliser les listes `[0, 3, -5, 9]`, `[10, 0, 0, 9]` et `[8, 5, 1, -1]` pour les représenter en Haskell. -Maintenant, pour les sommer, il suffit de faire : + 5, 9]`, `[10, 0, 0, 9]` et `[8, 5, 1, -1]` pour les représenter en Haskell. + Maintenant, pour les sommer, il suffit de faire : > ghci> map sum $ transpose [[0,3,5,9],[10,0,0,9],[8,5,1,-1]] > [18,8,6,17] @@ -177,10 +183,10 @@ souvent avoir des erreurs de débordement de pile. Le coupable est la nature paresseuse des plis, la valeur de l'accumulateur n'étant pas réellement mise à jour pendant la phase de pli. Ce qui se passe en réalité, c'est que l'accumulateur fait en quelque sorte la promesse qu'il se rappellera comment -calculer sa valeur quand on la lui demandera (NDT : pour cela, ce qu'on appelle -un glaçon, traduction de *thunk*, est créé). Cela a lieu pour chaque -accumulateur intermédiaire, et tous les glaçons sont empilés sur votre pile, et -finissent par la faire déborder. Les plis stricts ne sont pas de vils +calculer sa valeur quand on la lui demandera (NDT : pour cela, ce qu'on + appelle un glaçon, traduction de *thunk*, est créé). Cela a lieu pour +chaque accumulateur intermédiaire, et tous les glaçons sont empilés sur votre +pile, et finissent par la faire déborder. Les plis stricts ne sont pas de vils paresseux, et calculent les valeurs intermédiaires en route au lieu de remplir votre pile de glaçons. Donc, si jamais vous rencontrez des problèmes de débordement de pile en faisant des plis paresseux, essayez d'utiliser plutôt @@ -264,10 +270,10 @@ utile. > "This" Si l'on cherchait la somme de toutes les puissances de 3 inférieures à 10 000, -on ne pourrait pas mapper `(^3)` à `[1..]`, puis appliquer un filtre et sommer -le résultat, parce que `filter` ne termine pas sur des listes infinies. Vous -savez peut-être que les éléments sont croissants, mais Haskell ne le sait pas. -Vous pouvez donc faire ça : + on ne pourrait pas mapper `(^3)` à `[1..]`, puis appliquer un filtre et + sommer le résultat, parce que `filter` ne termine pas sur des listes + infinies. Vous savez peut-être que les éléments sont croissants, mais + Haskell ne le sait pas. Vous pouvez donc faire ça : > ghci> sum $ takeWhile (<10000) $ map (^3) [1..] > 53361 @@ -336,7 +342,7 @@ compte de chaque élément. > [(1,4),(2,7),(3,2),(5,1),(6,1),(7,1)] `inits` et `tails` sont comme `init` et `tail`, mais appliquent ces fonctions -récursivement tant que faire se peut. Observez : +récursivement tant que faire se peut. Observez : > ghci> inits "w00t" > ["","w","w0","w00","w00t"] @@ -390,7 +396,7 @@ seconde tous les autres. > ghci> partition (>3) [1,3,5,6,3,2,1,0,3,7] > ([5,6,7],[1,3,3,2,1,0,3]) -Il est important de saisir la différence avec `span` et `break` : +Il est important de saisir la différence avec `span` et `break` : > ghci> span (`elem` ['A'..'Z']) "BOBsidneyMORGANeddy" > ("BOB","sidneyMORGANeddy") @@ -403,11 +409,12 @@ toute la liste et la découpe conformément au prédicat. liste satisfaisant le prédicat. Mais il retourne ce prédicat encapsulé dans une valeur de type `Maybe`. Nous couvrirons les types de données algébriques plus en profondeur dans le prochain chapitre, mais pour l'instant, voilà ce que vous -avez besoin de savoir : une valeur `Maybe` peut soit être `Just something`, soit -`Nothing`. Tout comme une liste peut être soit la liste vide, soit une liste -avec des éléments, une valeur `Maybe` peut contenir soit aucun, soit un élement. -Et comme le type d'une liste d'entiers est par exemple `[Int]`, le type d'un -éventuel entier est `Maybe Int`. Bon, faisons un tour de `find` : +avez besoin de savoir : une valeur `Maybe` peut soit être `Just +something`, soit `Nothing`. Tout comme une liste peut être soit la liste vide, + soit une liste avec des éléments, une valeur `Maybe` peut contenir soit + aucun, soit un élement. Et comme le type d'une liste d'entiers est par + exemple `[Int]`, le type d'un éventuel entier est `Maybe Int`. Bon, faisons + un tour de `find` : > ghci> find (>4) [1,2,3,4,5,6] > Just 5 @@ -570,8 +577,8 @@ que celui passé en paramètre, et va alors insérer ce dernier devant l'autre. Le `4` est inséré juste après le `3` et juste avant le `5` dans le premier exemple, et entre le `3` et le `4` dans le second. -Propriété intéressante : si l'on utilise `insert` pour insérer dans une liste -triée, la liste reste triée. +Propriété intéressante : si l'on utilise `insert` pour insérer dans une +liste triée, la liste reste triée. > ghci> insert 4 [1,2,3,5,6,7] > [1,2,3,4,5,6,7] @@ -620,20 +627,20 @@ s'ils sont tous les deux positifs ou tous les deux négatifs. Elle pourrait aussi être écrite `\x y -> (x > 0) && (y > 0) || (x <= 0) && (y <= 0)`, bien que je trouve la première version plus lisible. Une manière encore plus claire d'écrire les fonctions d'égalité pour les fonctions en *By* consiste à importer -`on` depuis `Data.Function`. `on` est définie ainsi : +`on` depuis `Data.Function`. `on` est définie ainsi : > on :: (b -> b -> c) -> (a -> b) -> a -> a -> c > f `on` g = \x y -> f (g x) (g y) Ainsi, faire (==) \`on\` (> 0) retourne une fonction d'égalité qui ressemble à `\x y -> (x > 0) == (y > 0)`. `on` est très utilisée avec les -fonctions *By* parce qu'avec elle, on peut faire : +fonctions *By* parce qu'avec elle, on peut faire : > ghci> groupBy ((==) `on` (> 0)) values > [[-4.3,-2.4,-1.2],[0.4,2.3,5.9,10.5,29.1,5.3],[-2.4,-14.5],[2.9,2.3]] -Super lisible, non ? Vous pouvez le lire tout haut : groupe ceci par égalité -sur le fait que les éléments soient plus grands que zéro. +Super lisible, non ? Vous pouvez le lire tout haut : groupe ceci par +égalité sur le fait que les éléments soient plus grands que zéro. De façon similaire, les fonctions `sort`, `insert`, `maximum` et `minimum` ont aussi un équivalent plus général. Les fonctions comme `groupBy` prenaient une @@ -675,11 +682,11 @@ agissent sur des caractères. Et qui servent aussi pour filtrer ou mapper sur des chaînes de caractères, puisque ce ne sont que des listes de caractères. `Data.Char` exporte tout un tas de prédicats sur les caractères. C'est-à-dire, -des fonctions qui prennent un caractère et nous indiquent s'il satisfait une -condition. Les voici : + des fonctions qui prennent un caractère et nous indiquent s'il satisfait + une condition. Les voici : -`isControl` vérifie si c'est un caractère de contrôle (NDT : caractères non -affichables du sous-ensemble Latin-1 d'Unicode). +`isControl` vérifie si c'est un caractère de contrôle (NDT : caractères + non affichables du sous-ensemble Latin-1 d'Unicode). `isSpace` vérifie si c'est un caractère d'espacement. Cela inclue les espaces, les tabulations, les nouvelles lignes, etc. @@ -873,7 +880,7 @@ pouvoir récupérer le bon numéro de téléphone pour une personne donnée. La façon la plus évidente de représenter des listes associatives en Haskell est sous forme de listes de paires. La première composante de la paire serait la clé, la seconde serait la valeur. Voici un exemple de liste associative de -numéros de téléphone : +numéros de téléphone : > phoneBook = > [("betty","555-2938") @@ -922,11 +929,11 @@ pli.
    -**Note :** il est généralement mieux d'utiliser des plis pour effectuer de la -récursivité standard sur les listes plutôt que d'écrire explicitement la +**Note :** il est généralement mieux d'utiliser des plis pour effectuer de +la récursivité standard sur les listes plutôt que d'écrire explicitement la récursivité, car c'est plus simple à lire et à identifier. Tout le monde connaît les plis et sait en identifier un lorsqu'il voit un appel à `foldr`, -mais cela prend plus de temps de déchiffrer une récursivité explicite. + mais cela prend plus de temps de déchiffrer une récursivité explicite.
    @@ -967,8 +974,8 @@ map avec les mêmes associations. > ghci> Map.fromList [(1,2),(3,4),(3,2),(5,5)] > fromList [(1,2),(3,2),(5,5)] -S'il y a des clés en doublon dans la liste originale, les doublons sont ignorés. -Voici la signature de type de `fromList` : +S'il y a des clés en doublon dans la liste originale, les doublons sont +ignorés. Voici la signature de type de `fromList` : > Map.fromList :: (Ord k) => [(k, v)] -> Map.Map k v @@ -1002,7 +1009,7 @@ identique à l'ancienne, avec en plus la nouvelle clé-valeur insérée. > fromList [(3,100),(4,200),(5,600)] On peut implémenter notre propre `fromList` en utilisant la map vide, `insert` -et un pli. Observez : +et un pli. Observez : > fromList' :: (Ord k) => [(k,v)] -> Map.Map k v > fromList' = foldr (\(k,v) acc -> Map.insert k v acc) Map.empty @@ -1063,7 +1070,7 @@ dans la map. `fromList`, mais elle ne jette pas les clés en double, et utilise une fonction passée en paramètre pour décider quoi faire d'elles. Disons qu'une fille peut avoir plusieurs numéros de téléphone, et qu'on a une liste associative comme -celle-ci : +celle-ci : > phoneBook = > [("betty","555-2938") @@ -1079,7 +1086,7 @@ celle-ci : > ] Maintenant, si on utilise seulement `fromList` pour transformer cela en map, on -va perdre quelques numéros ! Voilà ce qu'on va faire : +va perdre quelques numéros ! Voilà ce qu'on va faire : > phoneBookToMap :: (Ord k) => [(k, String)] -> Map.Map k String > phoneBookToMap xs = Map.fromListWith (\number1 number2 -> number1 ++ ", " ++ number2) xs @@ -1143,7 +1150,7 @@ liste. Les noms dans `Data.Set` collisionnent beaucoup avec le `Prelude` et `Data.List`, on fait donc un import qualifié. -Placez cette déclaration dans un script : +Placez cette déclaration dans un script : > import qualified Data.Set as Set @@ -1282,7 +1289,7 @@ spécifions les fonctions qu'il exporte, et après cela, on peut commencer à > ) where Comme vous pouvez le voir, nous allons faire des aires et des volumes de -sphères, de cubes et de pavés droits. Définissons nos fonctions : +sphères, de cubes et de pavés droits. Définissons nos fonctions : > module Geometry > ( sphereVolume @@ -1332,7 +1339,7 @@ ces fonctions complètement ou de les effacer dans une nouvelle version (on pourrait supprimer `rectangleArea` et utiliser `*` à la place) et personne ne s'en souciera parce qu'on ne les avait pas exportées. -Pour utiliser notre module, on fait juste : +Pour utiliser notre module, on fait juste : > import Geometry @@ -1345,8 +1352,9 @@ Découpons ces fonctions de manière à ce que `Geometry` soit un module avec trois sous-modules, un pour chaque type d'objet. D'abord, créons un dossier `Geometry`. Attention à la majuscule à G. Dans ce -dossier, placez trois dossiers : `Sphere.hs`, `Cuboid.hs` et `Cube.hs` (NDT : -"Cuboid" signifie pavé droit). Voici ce que les fichiers contiennent : +dossier, placez trois dossiers : `Sphere.hs`, `Cuboid.hs` et `Cube.hs` +(NDT : "Cuboid" signifie pavé droit). Voici ce que les fichiers +contiennent : `Sphere.hs` @@ -1402,14 +1410,14 @@ fonctions ayant le même nom que celles de `Geometry.Cube`. C'est pourquoi l'import est qualifié, et tout va bien. Donc maintenant, si l'on se trouve dans un fichier qui se trouve au même niveau -que le dossier `Geometry`, on peut par exemple : +que le dossier `Geometry`, on peut par exemple : > import Geometry.Sphere Et maintenant, on peut utiliser `area` et `volume`, qui nous donneront l'aire et le volume d'une sphère. Et si l'on souhaite jongler avec deux ou plus de ces modules, on doit utiliser des imports qualifiés car ils exportent des fonctions -avec des noms identiques. Tout simplement : +avec des noms identiques. Tout simplement : > import qualified Geometry.Sphere as Sphere > import qualified Geometry.Cuboid as Cuboid @@ -1427,13 +1435,19 @@ autre programme. diff --git a/pour-une-poignee-de-monades.mkd b/pour-une-poignee-de-monades.mkd index 200f607..cd18cee 100644 --- a/pour-une-poignee-de-monades.mkd +++ b/pour-une-poignee-de-monades.mkd @@ -3,13 +3,19 @@ @@ -29,14 +35,14 @@ applicatifs étaient juste des foncteurs gonflés aux hormones. Quand on a débuté les foncteurs, on a vu qu'il était possible de mapper des fonctions sur divers types de données. On a vu qu'à cet effet, la classe de -types `Functor` avait été introduite, et cela a soulevé la question : quand on -a une fonction de type `a -> b` et un type de données `f a`, comment mapper +types `Functor` avait été introduite, et cela a soulevé la question  quand +on a une fonction de type `a -> b` et un type de données `f a`, comment mapper cette fonction sur le type de données pour obtenir un `f b` ? On a vu comment mapper quelque chose sur un `Maybe a`, une liste `[a]`, une action I/O `IO a`, etc. On a même vu comment mapper une fonction `a -> b` sur d'autres fonctions de type `r -> a` pour obtenir des fonctions `r -> b`. Pour répondre à la question de comment mapper une fonction sur un type de données, tout ce qu'il -nous a fallu considérer était le type de `fmap` : +nous a fallu considérer était le type de `fmap`  > fmap :: (Functor f) => (a -> b) -> f a -> f b @@ -50,7 +56,7 @@ cela à `Just 5` ? Et si l'on voulait l'appliquer non pas à `Just 5`, mais plutôt à `Nothing` ? Ou si l'on avait `[(*2), (+4)]`, comment l'appliquerions-nous à `[1, 2, 3]` ? Comment cela marcherait-il ? Pour cela, la classe de types `Applicative` est introduite, afin de répondre au problème à -l'aide du type : +l'aide du type  > (<*>) :: (Applicative f) => f (a -> b) -> f a -> f b @@ -68,7 +74,7 @@ d'être un `Char`, c'est un `Maybe Char`, ce qui nous indique que sa valeur peut La façon dont la classe de types `Applicative` nous permettait d'utiliser des fonctions normales sur des valeurs dans des contextes en préservant le contexte -était très propre. Observez : +était très propre. Observez  > ghci> (*) <$> Just 2 <*> Just 8 > Just 16 @@ -84,11 +90,11 @@ déterministes), les `IO a` représentent des valeurs qui ont des effets de bord etc. Les monades sont une extension naturelle des foncteurs applicatifs, et avec -elles on cherche à résoudre ceci : si vous avez une valeur dans un contexte, `m -a`, comment appliquer dessus une fonction qui prend une valeur normale `a` et -retourne une valeur dans un contexte ? C'est-à-dire, comment appliquer une -fonction de type `a -> m b` à une valeur de type `m a` ? En gros, on veut cette -fonction : +elles on cherche à résoudre ceci  si vous avez une valeur dans un +contexte, `m a`, comment appliquer dessus une fonction qui prend une valeur +normale `a` et retourne une valeur dans un contexte ? C'est-à-dire, comment +appliquer une fonction de type `a -> m b` à une valeur de type `m a` ? En gros, +on veut cette fonction  > (>>=) :: (Monad m) => m a -> (a -> m b) -> m b @@ -97,7 +103,7 @@ mais retourne une valeur spéciale, comment donner la valeur spéciale à cette fonction ?** C'est la question à laquelle on s'attaquera en étudiant les monades. On écrit `m a` plutôt que `f a` parce que le `m` signifie `Monad`, mais les monades sont seulement des foncteurs applicatifs supportant `(>>=)`. -La fonction `(>>=)` est prononcée *bind* (NDT : en français, cela signifie +La fonction `(>>=)` est prononcée *bind* (NDT  en français, cela signifie lier). Quand on a une valeur normale `a` et une fonction normale `a -> b`, il est très @@ -142,7 +148,7 @@ une fonction sur celui-ci, elle est mappée sur ce qui est à l'intérieur des valeurs `Just`, les `Nothing` étant conservés tels quels parce qu'il n'y a pas de valeur sur laquelle mapper ! -Ainsi : +Ainsi  > ghci> fmap (++"!") (Just "wisdom") > Just "wisdom!" @@ -156,7 +162,7 @@ fonction dans un `Maybe` à une valeur dans un `Maybe`, elles doivent toutes deux être des valeurs `Just` pour que le résultat soit une valeur `Just`, autrement le résultat est `Nothing`. C'est logique, puisque s'il vous manque soit la fonction, soit son paramètre, vous ne pouvez pas l'inventer, donc vous -devez propager l'échec : +devez propager l'échec  > ghci> Just (+3) <*> Just 3 > Just 6 @@ -185,7 +191,7 @@ Dans ce cas, `>>=` prendrait un `Maybe a` et une fonction de type `a -> Maybe b` et appliquerait la fonction au `Maybe a`. Pour comprendre comment elle fait cela, on peut utiliser notre intuition venant du fait que `Maybe` est un foncteur applicatif. Mettons qu'on ait une fonction `\x -> Just (x + 1)`. Elle -prend un nombre, lui ajoute `1` et l'enveloppe dans un `Just` : +prend un nombre, lui ajoute `1` et l'enveloppe dans un `Just`  > ghci> (\x -> Just (x+1)) 1 > Just 2 @@ -193,7 +199,7 @@ prend un nombre, lui ajoute `1` et l'enveloppe dans un `Just` : > Just 101 Si on lui donne `1`, elle s'évalue en `Just 2`. Si on lui donne le nombre -`100`, le résultat est `Just 101`. Très simple. Voilà l'obstacle : comment +`100`, le résultat est `Just 101`. Très simple. Voilà l'obstacle  comment donner une valeur `Maybe` a cette fonction ? Si on pense à ce que fait `Maybe` en tant que foncteur applicatif, répondre est plutôt simple. Si on lui donne une valeur `Just`, elle prend ce qui est dans le `Just` et applique la fonction @@ -203,14 +209,14 @@ rien pour l'appliquer. Dans ce cas, faisons juste comme avant et retournons Plutôt que de l'appeler `>>=`, appelons-la `applyMaybe` pour l'instant. Elle prend un `Maybe a` et une fonction et retourne un `Maybe b`, parvenantt à -appliquer la fonction sur le `Maybe a`. Voici le code : +appliquer la fonction sur le `Maybe a`. Voici le code  > applyMaybe :: Maybe a -> (a -> Maybe b) -> Maybe b > applyMaybe Nothing f = Nothing > applyMaybe (Just x) f = f x Ok, à présent jouons un peu. On va l'utiliser en fonction infixe pour que la -valeur `Maybe` soit sur le côté gauche et la fonction sur la droite : +valeur `Maybe` soit sur le côté gauche et la fonction sur la droite  > ghci> Just 3 `applyMaybe` \x -> Just (x+1) > Just 4 @@ -224,7 +230,7 @@ valeur `Maybe` soit sur le côté gauche et la fonction sur la droite : Dans l'exemple ci-dessus, on voit que quand on utilisait `applyMaybe` avec une valeur `Just` et une fonction, la fonction était simplement appliquée dans le `Just`. Quand on essayait de l'utiliser sur un `Nothing`, le résultat était -`Nothing`. Et si la fonction retourne `Nothing` ? Voyons : +`Nothing`. Et si la fonction retourne `Nothing` ? Voyons  > ghci> Just 3 `applyMaybe` \x -> if x > 2 then Just x else Nothing > Just 3 @@ -259,8 +265,8 @@ La classe de types Monad Tout comme les foncteurs ont la classe de types `Functor` et les foncteurs applicatifs ont la classe de types `Applicative`, les monades viennent avec -leur propre classe de types : `Monad` ! Wow, qui l'eut cru ? Voici à quoi -ressemble la classe de types : +leur propre classe de types  `Monad` ! Wow, qui l'eut cru ? Voici à quoi +ressemble la classe de types  > class Monad m where > return :: a -> m a @@ -297,8 +303,8 @@ valeur. Pour `Maybe` elle prend une valeur et l'enveloppe dans `Just`.
    -Un petit rappel : `return` n'est en rien comme le `return` qui existe dans la -plupart des langages. Elle ne termine pas l'exécution de la fonction ou quoi +Un petit rappel  `return` n'est en rien comme le `return` qui existe dans +la plupart des langages. Elle ne termine pas l'exécution de la fonction ou quoi que ce soit, elle prend simplement une valeur normale et la place dans un contexte. @@ -339,7 +345,7 @@ gauche est `Nothing`, parce qu'il n'y a pas de valeur sur laquelle appliquer la fonction. Si c'est un `Just`, on prend ce qui est à l'intérieur et on applique `f` dessus. -On peut jouer un peu avec la monade `Maybe` : +On peut jouer un peu avec la monade `Maybe`  > ghci> return "WHAT" :: Maybe String > Just "WHAT" @@ -372,9 +378,9 @@ plusieurs valeurs `Maybe a`. Pierre a décidé de faire une pause dans son emploi au centre de pisciculture pour s'adonner au funanbulisme. Il n'est pas trop mauvais, mais il a un -problème : des oiseaux n'arrêtent pas d'atterrir sur sa perche d'équilibre ! -Ils viennent se reposer un instant, discuter avec leurs amis aviaires, avant de -décoller en quête de miettes de pain. Cela ne le dérangerait pas tant si le +problème  des oiseaux n'arrêtent pas d'atterrir sur sa perche d'équilibre +! Ils viennent se reposer un instant, discuter avec leurs amis aviaires, avant +de décoller en quête de miettes de pain. Cela ne le dérangerait pas tant si le nombre d'oiseaux sur le côté gauche de la perche était égal à celui sur le côté droit. Mais parfois, tous les oiseaux décident qu'ils préfèrent le même côté, et ils détruisent son équilibre, l'entraînant dans une chute embarassante (il a @@ -392,7 +398,8 @@ arrive sur sa gauche, puis quatre oiseaux débarquent sur sa droite, et enfin l'oiseau sur sa gauche décide de s'envoler. On peut représenter la perche comme une simple paire d'entiers. La première -composante compte les oiseaux sur sa gauche, la seconde ceux sur sa droite : +composante compte les oiseaux sur sa gauche, la seconde ceux sur sa +droite  > type Birds = Int > type Pole = (Birds,Birds) @@ -403,7 +410,7 @@ synonyme pour `(Birds, Birds)` qu'on appelle `Pole` (à ne pas confondre avec une personne d'origine polonaise). Ensuite, pourquoi ne pas créer une fonction qui prenne un nombre d'oiseaux et -les fait atterrir sur un côté de la perche ? Voici les fonctions : +les fait atterrir sur un côté de la perche ? Voici les fonctions  > landLeft :: Birds -> Pole -> Pole > landLeft n (left,right) = (left + n,right) @@ -411,7 +418,7 @@ les fait atterrir sur un côté de la perche ? Voici les fonctions : > landRight :: Birds -> Pole -> Pole > landRight n (left,right) = (left,right + n) -Simple. Essayons-les : +Simple. Essayons-les  > ghci> landLeft 2 (0,0) > (2,0) @@ -422,7 +429,7 @@ Simple. Essayons-les : Pour faire décoller les oiseaux, on a utilisé un nombre négatif. Puisque faire atterrir des oiseaux sur un `Pole` retourne un `Pole`, on peut chaîner les -applications de `landLeft` et `landRight` : +applications de `landLeft` et `landRight`  > ghci> landLeft 2 (landRight 1 (landLeft 1 (0,0))) > (3,1) @@ -432,12 +439,12 @@ Puis, on fait atterrir un oiseau sur le côté droit, ce qui donne `(1, 1)`. Finalement, deux oiseaux atterrisent à gauche, donnant `(3, 1)`. On applique une fonction en écrivant d'abord la fonction puis le paramètre, mais ici, il serait plus pratique d'avoir la perche en première et ensuite la fonction -d'atterrissage. Si l'on crée une fonction : +d'atterrissage. Si l'on crée une fonction  > x -: f = f x On peut appliquer les fonctions en écrivant d'abord le paramètre puis la -fonction : +fonction  > ghci> 100 -: (*3) > 300 @@ -447,7 +454,7 @@ fonction : > (2,0) Ainsi, on peut faire atterrir de façon répétée des oiseaux, de manière plus -lisible : +lisible  > ghci> (0,0) -: landLeft 1 -: landRight 1 -: landLeft 2 > (3,1) @@ -465,7 +472,7 @@ atterrissent du même côté ? 10 oiseaux à gauche et seulement 3 à droite ? Avec ça, le pauvre Pierre est sûr de se casser la figure ! Ici, c'est facile de s'en rendre compte, mais si l'on -avait une séquence comme : +avait une séquence comme  > ghci> (0,0) -: landLeft 1 -: landRight 4 -: landLeft (-1) -: landRight (-2) > (0,2) @@ -477,7 +484,7 @@ et `landRight`. De ce qu'on voit, on veut que ces fonctions puissent échouer. C'est-à-dire, on souhaite qu'elles retournent une nouvelle perche tant que l'équilibre est respecté, mais qu'elles échouent si les oiseaux atterrissent en disproportion. Et quel meilleur moyen d'ajouter un contexte de possible échec -que d'utiliser une valeur Maybe ? Reprenons ces fonctions : +que d'utiliser une valeur Maybe ? Reprenons ces fonctions  > landLeft :: Birds -> Pole -> Maybe Pole > landLeft n (left,right) @@ -496,7 +503,7 @@ des gardes pour vérifier si la différence entre les extrémités de la perche inférieure à 4. Si c'est le cas, on enveloppe la nouvelle perche dans un `Just` et on la retourne. Sinon, on retourne `Nothing` pour indiquer l'échec. -Mettons ces nouvelles-nées à l'épreuve : +Mettons ces nouvelles-nées à l'épreuve  > ghci> landLeft 2 (0,0) > Just (2,0) @@ -513,7 +520,7 @@ un `Maybe Pole`. `landLeft 1` prend un `Pole` et retourne un `Maybe Pole`. On a besoin d'une manière de prendre un `Maybe Pole` et de le donner à une fonction qui prend un `Pole` et retourne un `Maybe Pole`. Heureusement, on a -`>>=`, qui fait exactement ça pour `Maybe`. Essayons : +`>>=`, qui fait exactement ça pour `Maybe`. Essayons  > ghci> landRight 1 (0,0) >>= landLeft 2 > Just (2,1) @@ -523,7 +530,7 @@ lui donner directement un `Maybe Pole` résultant de `landRight 1 (0, 0)`, alors on utilise `>>=` pour prendre une valeur dans un contexte et la passer à `landLeft 2`. `>>=` nous permet en effet de traiter la valeur `Maybe` comme une valeur avec un contexte, parce que si l'on donne `Nothing` à `landLeft 2`, on -obtient bien `Nothing` et l'échec est propagé : +obtient bien `Nothing` et l'échec est propagé  > ghci> Nothing >>= landLeft 2 > Nothing @@ -532,7 +539,7 @@ Ainsi, on peut à présent chaîner des atterrissages pouvant échouer parce que `>>=` nous permet de donner une valeur monadique à une fonction qui attend une valeur normale. -Voici une séquence d'atterrisages : +Voici une séquence d'atterrisages  > ghci> return (0,0) >>= landRight 2 >>= landLeft 2 >>= landRight 2 > Just (2,4) @@ -545,7 +552,7 @@ chaque fonction. `Just (0, 0)` alimente `landRight 2`, résultant en `Just (0, de suite. Souvenez vous de l'exemple précédent où l'on introduisait la notion d'échec -dans la routine de Pierre : +dans la routine de Pierre  > ghci> (0,0) -: landLeft 1 -: landRight 4 -: landLeft (-1) -: landRight (-2) > (0,2) @@ -587,14 +594,14 @@ atterrissage, le résultat possible de l'atterrissage précédent est examiné p voir l'équilibre de la perche. Cela détermine l'échec ou non de l'atterrissage. On peut aussi créer une fonction qui ignore le nombre d'oiseaux sur la perche -et fait tomber Pierre. On peut l'appeler `banana` : +et fait tomber Pierre. On peut l'appeler `banana`  > banana :: Pole -> Maybe Pole > banana _ = Nothing On peut maintenant la chaîner à nos atterrissages d'oiseaux. Elle causera toujours la chute de notre funambule, parce qu'elle ignore ce qu'on lui passe -et retourne un échec. Regardez : +et retourne un échec. Regardez  > ghci> return (0,0) >>= landLeft 1 >>= banana >>= landRight 1 > Nothing @@ -604,7 +611,7 @@ qui force le tout à résulter en `Nothing`. Comme c'est triste ! Plutôt que de créer des fonctions qui ignorent leur entrée et retournent une valeur monadique prédéterminée, on peut utiliser la fonction `>>`, dont -l'implémentation par défaut est : +l'implémentation par défaut est  > (>>) :: (Monad m) => m a -> m b -> m b > m >> n = m >>= \_ -> n @@ -613,7 +620,7 @@ Normalement, passer une valeur à une fonction qui ignore son paramètre et retourne tout le temps une valeur prédéterminée résulterait toujours en cette valeur prédéterminée. Avec les monades cependant, leur contexte et signification doit être également considéré. Voici comment `>>` agit sur -`Maybe` : +`Maybe`  > ghci> Nothing >> Just 3 > Nothing @@ -626,7 +633,7 @@ Si vous remplacez `>>` par `>>= \_ ->`, il est facile de voir pourquoi cela arrive. On peut remplacer notre fonction `banana` dans la chaîne par un `>>` suivi d'un -`Nothing` : +`Nothing`  > ghci> return (0,0) >>= landLeft 1 >> Nothing >>= landRight 1 > Nothing @@ -636,7 +643,7 @@ Et voilà, échec évident garanti ! Il est aussi intéressant de regarder ce qu'on aurait dû écrire si l'on avait pas fait le choix astucieux de traiter les valeurs `Maybe` comme des valeurs dans un contexte pour les donner à nos fonctions. Voici à quoi une série -d'atterrissages aurait ressemblé : +d'atterrissages aurait ressemblé  > routine :: Maybe Pole > routine = case landLeft 1 (0,0) of @@ -681,11 +688,11 @@ spéciale appelée notation *do*. On a déjà rencontré la notation *do* quand faisait des entrées-sorties, et on a alors dit qu'elle servait à coller plusieurs actions I/O en une seule. Eh bien, il s'avère que la notation *do* n'est pas seulement pour `IO`, mais peut être utilisée pour n'importe quelle -monade. Le principe est identique : coller ensemble plusieurs valeurs +monade. Le principe est identique  coller ensemble plusieurs valeurs monadiques en séquence. Nous allons regarder comment la notation *do* fonctionne et en quoi elle est utile. -Considérez l'exemple familier de l'application monadique : +Considérez l'exemple familier de l'application monadique  > ghci> Just 3 >>= (\x -> Just (show x ++ "!")) > Just "3!" @@ -694,7 +701,7 @@ Déjà vu. Donner une valeur monadique à une fonction qui en retourne une autre rien de grandiose. Remarquez comme en faisant ainsi, `x` devient `3` à l'intérieur de la lambda. Une fois dans la lambda, c'est juste une valeur normale plutôt qu'une valeur monadique. Et si l'on avait encore un `>>=` dans -cette fonction ? Regardez : +cette fonction ? Regardez  > ghci> Just 3 >>= (\x -> Just "!" >>= (\y -> Just (show x ++ y))) > Just "3!" @@ -702,14 +709,15 @@ cette fonction ? Regardez : Ah, une utilisation imbriquée de `>>=` ! Dans la lambda la plus à l'extérieur, on donne `Just "!"` à la lambda `\y -> Just (show x ++ y)`. Dans cette lambda là, le `y` devient `"!"`, et le `x` est toujours `3` parce qu'on l'obtient de -la lambda la plus englobante. Cela me rappelle un peu l'expression suivante : +la lambda la plus englobante. Cela me rappelle un peu l'expression +suivante  > ghci> let x = 3; y = "!" in show x ++ y > "3!" La différence principale entre les deux, c'est que les valeurs dans la première sont monadiques. Ce sont des valeurs dans le contexte de l'échec. On peut -remplacer n'importe laquelle d'entre elle par un échec : +remplacer n'importe laquelle d'entre elle par un échec  > ghci> Nothing >>= (\x -> Just "!" >>= (\y -> Just (show x ++ y))) > Nothing @@ -726,7 +734,7 @@ C'est donc un peu comme assigner des valeurs à des variables dans des expressions *let*, seulement les valeurs sont monadiques. Pour mieux illustrer ce point, écrivons un script dans lequel chaque `Maybe` -prend une ligne : +prend une ligne  > foo :: Maybe String > foo = Just 3 >>= (\x -> @@ -735,7 +743,7 @@ prend une ligne : Pour nous éviter d'écrire ces énervantes lambdas successives, Haskell nous offre la notation *do*. Elle nous permet de réécrire le bout de code précédent -ainsi : +ainsi  > foo :: Maybe String > foo = do @@ -764,14 +772,14 @@ d'applications avec `>>=`. Plutôt, le résultat de cette dernière expression e le résultat de la valeur monadique entière, prenant en compte l'échec possible des précédentes. -Par exemple, examinez la ligne suivante : +Par exemple, examinez la ligne suivante  > ghci> Just 9 >>= (\x -> Just (x > 8)) > Just True Puisque le paramètre de gauche de `>>=` est une valeur `Just`, la lambda est appliquée à `9` et le résultat est `Just True`. Si l'on réécrit cela en -notation *do*, on obtient : +notation *do*, on obtient  > marySue :: Maybe Bool > marySue = do @@ -797,7 +805,7 @@ chacune ajoutait un contexte d'échec éventuel. Voici deux oiseaux atterrissant > second <- landRight 2 first > landLeft 1 second -Voyons si cela réussit : +Voyons si cela réussit  > ghci> routine > Just (3,2) @@ -818,7 +826,7 @@ résultat des précédentes, ainsi que de leur contexte (dans ce cas, savoir si elles ont réussi ou échoué). Encore une fois, regardons à quoi ressemblerait le code si nous n'avions pas -utiliser les aspects monadiques de `Maybe` : +utiliser les aspects monadiques de `Maybe`  > routine :: Maybe Pole > routine = @@ -834,7 +842,7 @@ Voyez comme dans le cas d'un succès, le tuple à l'intérieur de `Just (0, 0)` devient `start`, le résultat de `landLeft 2 start` devient `first`, etc. Si l'on veut lancer à Pierre une peau de banane avec la notation *do*, on peut -le faire ainsi : +le faire ainsi  > routine :: Maybe Pole > routine = do @@ -861,7 +869,7 @@ la notation *do*. Avec la notation *do*, lorsqu'on lie des valeurs monadiques à des noms, on peut utiliser du filtrage par motif, tout comme dans les expressions `let` et les paramètres de fonctions. Voici un exemple de filtrage par motif dans une -expression `do` : +expression `do`  > justH :: Maybe Char > justH = do @@ -881,21 +889,21 @@ mécanisme de motif suivant. Quand un filtrage par motif échoue dans une expression `do`, la fonction `fail` est appelée. Elle fait partie de la classe de types `Monad` et permet à un filtrage par motif échoué de résulter en un échec dans le contexte de cette monade, plutôt que de planter notre programme. -Son implémentation par défaut est : +Son implémentation par défaut est  > fail :: (Monad m) => String -> m a > fail msg = error msg Donc, par défaut, elle plante notre programme, mais les monades qui contiennent un contexte d'échec éventuel (comme `Maybe`) implementent généralement leur -propre version. Pour `Maybe`, elle est implémentée ainsi : +propre version. Pour `Maybe`, elle est implémentée ainsi  > fail _ = Nothing Elle ignore le message et crée un `Nothing`. Ainsi, quand le filtrage par motif échoue pour une valeur `Maybe` dans une notation *do*, la valeur de la monade entière est `Nothing`. C'est préférable à un plantage du programme. Voici une -expression `do` avec un motif voué à l'échec : +expression `do` avec un motif voué à l'échec  > wopwop :: Maybe Char > wopwop = do @@ -903,7 +911,7 @@ expression `do` avec un motif voué à l'échec : > return x Le filtrage par motif échoue, donc l'effet est le même que si la ligne entière -comprenant le motif était remplacée par `Nothing`. Essayons : +comprenant le motif était remplacée par `Nothing`. Essayons  > ghci> wopwop > Nothing @@ -929,7 +937,7 @@ valeur comme `5` est déterministe. Elle n'a qu'un résultat, et on sait exactement lequel c'est. D'un autre côté, une valeur comme `[3, 8, 9]` contient plusieurs résultats, on peut donc la voir comme une valeur qui est en fait plusieurs valeurs à la fois. Utiliser les listes comme des foncteurs -applicatifs démontre ce non déterminisme élégamment : +applicatifs démontre ce non déterminisme élégamment  > ghci> (*) <$> [1,2,3] <*> [10,100,1000] > [10,100,1000,20,200,2000,30,300,3000] @@ -941,7 +949,7 @@ possibles, donc on les essaie tous, et le résultat est également une valeur no déterministe, avec encore plus de résultats. Ce contexte de non déterminisme se transfère très joliment aux monades. -Regardons immédiatement l'instance de `Monad` pour les listes : +Regardons immédiatement l'instance de `Monad` pour les listes  > instance Monad [] where > return x = [x] @@ -974,14 +982,14 @@ la donne à une fonction qui retourne également une valeur non déterministe. L résultat est également non déterministe, et contient tous les résultats possibles consistant à prendre un élément de la liste `[3, 4, 5]` et de le passer à la fonction `\x -> [x, -x]`. Cette fonction prend un nombre et produit -deux résultats : le nombre inchangé et son opposé. Ainsi, quand on utilise +deux résultats  le nombre inchangé et son opposé. Ainsi, quand on utilise `>>=` pour donner cette liste à la fonction, chaque nombre est gardé inchangé et opposé. Le `x` de la lambda prend chaque valeur de la liste qu'on donne à la fonction. Pour voir comment cela a lieu, on peut simplement suivre l'implémentation. D'abord, on comment avec la liste `[3, 4, 5]`. Puis, on mappe la lambda sur la -liste, et le résultat est : +liste, et le résultat est  > [[3,-3],[4,-4],[5,-5]] @@ -992,7 +1000,7 @@ non déterministe à une valeur non déterministe ! Le non déterminisme supporte également l'échec. La liste vide `[]` est presque l'équivalent de `Nothing`, parce qu'elle signifie l'absence de résultat. C'est pourquoi l'échec est défini comme la liste vide. Le message d'erreur est jeté. -Jouons avec des listes qui échouent : +Jouons avec des listes qui échouent  > ghci> [] >>= \x -> ["bad","mad","rad"] > [] @@ -1007,7 +1015,7 @@ ignoré et la fonction retourne une liste vide. Parce que la fonction échoue quelle que soit son entrée, le résultat est un échec. Tout comme les valeurs `Maybe`, on peut chaîner plusieurs listes avec `>>=`, -propageant le non déterminisme : +propageant le non déterminisme  > ghci> [1,2] >>= \n -> ['a','b'] >>= \ch -> return (n,ch) > [(1,'a'),(1,'b'),(2,'a'),(2,'b')] @@ -1018,9 +1026,9 @@ La liste `[1, 2]` est liée à `n` et `['a', 'b']` est liée à `ch`. Puis, on f `return (n, ch)` (ou `[(n, ch)]`), ce qui signifie prendre une paire `(n, ch)` et la placer dans un contexte minimal. Dans ce cas, c'est la liste la plus petite contenant la valeur `(n, ch)` et étant le moins non déterministe -possible. Son effet sur le contexte est donc minimal. Ce qu'on dit ici, c'est : -pour chaque élément dans `[1, 2]`, et pour chaque élément de `['a', 'b']`, -produit un tuple d'un élément de chaque liste. +possible. Son effet sur le contexte est donc minimal. Ce qu'on dit ici, +c'est  pour chaque élément dans `[1, 2]`, et pour chaque élément de `['a', +'b']`, produit un tuple d'un élément de chaque liste. En généal, parce que `return` prend une valeur et l'enveloppe dans un contexte minimal, elle n'a pas d'effet additionnel (comme l'échec pour `Maybe`, ou @@ -1035,7 +1043,7 @@ représente une branche.
    -Voici l'exemple précédent réécrit avec la notation *do* : +Voici l'exemple précédent réécrit avec la notation *do*  > listOfTuples :: [(Int,Char)] > listOfTuples = do @@ -1050,7 +1058,7 @@ normales, et `>>=` s'occupe du contexte pour nous. Le contexte dans ce cas est le non déterminisme. Utiliser les listes avec la notation *do* me rappelle quelque chose qu'on a -déjà vu. Regardez le code suivant : +déjà vu. Regardez le code suivant  > ghci> [ (n,ch) | n <- [1,2], ch <- ['a','b'] ] > [(1,'a'),(1,'b'),(2,'a'),(2,'b')] @@ -1071,7 +1079,7 @@ pour faire les calculs non déterministes. Les listes en compréhension nous permettent de filtrer la sortie. Par exemple, on peut filtrer une liste de nombres pour chercher seulement ceux qui -contiennent le chiffre `7` : +contiennent le chiffre `7`  > ghci> [ x | x <- [1..50], '7' `elem` show x ] > [7,17,27,37,47] @@ -1081,7 +1089,7 @@ et on vérifie si le caractère `7` fait partie de cette chaîne. Plutôt malin. Pour voir comment le filtrage dans les listes en compréhension se traduit dans la monade des listes, il nous faut regarder la fonction `guard` et la classe de types `MonadPlus`. La classe de type `MonadPlus` est pour les monades qui -peuvent aussi se comporter en monoïdes. Voici sa définition : +peuvent aussi se comporter en monoïdes. Voici sa définition  > class Monad m => MonadPlus m where > mzero :: m a @@ -1089,7 +1097,7 @@ peuvent aussi se comporter en monoïdes. Voici sa définition : `mzero` est synonyme de `mempty` de la classe de types `Monoid`, et `mplus` correspond à `mappend`. Parce que les listes sont des monoïdes ainsi que des -monades, elles peuvent être instance de cette classe de types : +monades, elles peuvent être instance de cette classe de types  > instance MonadPlus [] where > mzero = [] @@ -1097,7 +1105,7 @@ monades, elles peuvent être instance de cette classe de types : Pour les listes, `mzero` représente un calcul non déterministe qui n'a pas de résultats - un calcul échoué. `mplus` joint deux valeurs non déterministes en -une. La fonction `guard` est définie ainsi : +une. La fonction `guard` est définie ainsi  > guard :: (MonadPlus m) => Bool -> m () > guard True = return () @@ -1105,7 +1113,7 @@ une. La fonction `guard` est définie ainsi : Elle prend une valeur booléenne et si c'est `True`, elle prend un `()` et le place dans un contexte minimal par défaut qui réussisse toujours. Sinon, elle -crée une valeur monadique échouée. La voici en action : +crée une valeur monadique échouée. La voici en action  > ghci> guard (5 > 2) :: Maybe () > Just () @@ -1117,14 +1125,14 @@ crée une valeur monadique échouée. La voici en action : > [] Ça a l'air intéressant, mais comment est-ce utile ? Dans la monade des listes, -on l'utilise pour filtrer des calculs non déterministes. Observez : +on l'utilise pour filtrer des calculs non déterministes. Observez  > ghci> [1..50] >>= (\x -> guard ('7' `elem` show x) >> return x) > [7,17,27,37,47] Le résultat ici est le même que le résultat de la liste en compréhension précédente. Comment `guard` fait-elle cela ? Regardons d'abord comment elle -fonctionne en conjonction avec `>>` : +fonctionne en conjonction avec `>>`  > ghci> guard (5 > 2) >> return "cool" :: [String] > ["cool"] @@ -1135,11 +1143,11 @@ Si `guard` réussit, le résultat qu'elle contient est un tuple vide. On utilise alors `>>` pour ignorer ce typle vide et présenter quelque chose d'autre en retour. Cependant, si `guard` échoue, alors le `return` échouera aussi, parce que donner une liste vide à une fonction via `>>=` résulte toujours en une -liste vide. `guard` dit simplement : si ce booléen est `False`, alors produit -un échec immédiatement, sinon crée une valeur réussie factice avec `()`. Tout -ce que cela permet est d'autoriser le calcul à continuer. +liste vide. `guard` dit simplement  si ce booléen est `False`, alors +produit un échec immédiatement, sinon crée une valeur réussie factice avec +`()`. Tout ce que cela permet est d'autoriser le calcul à continuer. -Voici l'exemple précédent réécrit en notation *do* : +Voici l'exemple précédent réécrit en notation *do*  > sevensOnly :: [Int] > sevensOnly = do @@ -1149,7 +1157,7 @@ Voici l'exemple précédent réécrit en notation *do* : Si nous avions oublié de présenter `x` en résultat final avec `return`, la liste résultat serait seulement une liste de tuples vides. Voici la même chose -sous forme de liste en compréhension : +sous forme de liste en compréhension  > ghci> [ x | x <- [1..50], '7' `elem` show x ] > [7,17,27,37,47] @@ -1166,7 +1174,7 @@ colonne, et le second la ligne. hu je suis un cheval Créons un synonyme de type pour la position actuelle du cavalier sur -l'échiquier : +l'échiquier  > type KnightPos = (Int,Int) @@ -1176,7 +1184,7 @@ le meilleur mouvement à faire ensuite ? Je sais, pourquoi pas tous les mouvements possibles ! On a à notre disposition le non déterminisme, donc plutôt que d'avoir à choisir un mouvement, choisissons-les tous à la fois. Voici une fonction qui prend la position d'un cavalier et retourne toutes ses -prochaines positions : +prochaines positions  > moveKnight :: KnightPos -> [KnightPos] > moveKnight (c,r) = do @@ -1195,7 +1203,7 @@ l'échiquier. Si ce n'est pas le cas, elle produit une liste vide qui cause un Cette fonction peut être réécrite sans utiliser les listes comme des monades, mais on l'a fait quand même pour l'entraînement. Voici la même fonction avec -`filter` : +`filter`  > moveKnight :: KnightPos -> [KnightPos] > moveKnight (c,r) = filter onBoard @@ -1205,7 +1213,7 @@ mais on l'a fait quand même pour l'entraînement. Voici la même fonction avec > where onBoard (c,r) = c `elem` [1..8] && r `elem` [1..8] Les deux font la même chose, choisissez celle que vous trouvez plus jolie. -Essayons : +Essayons  > ghci> moveKnight (6,2) > [(8,1),(8,3),(4,1),(4,3),(7,4),(5,4)] @@ -1216,7 +1224,7 @@ Essayons : mouvements possibles à la fois, pour ainsi dire. À présent qu'on a une position non déterministe, on peut utiliser `>>=` pour la donner à `moveKnight`. Voici une fonction qui prend une position et retourne toutes les positions qu'on peut -atteindre dans trois mouvements : +atteindre dans trois mouvements  > in3 :: KnightPos -> [KnightPos] > in3 start = do @@ -1227,7 +1235,7 @@ atteindre dans trois mouvements : Si vous lui passez `(6, 2)`, la liste résultante est assez grande, parce que s'il y a beaucoup de façons d'atteindre une position en trois mouvements, alors le résultat est dans la liste de multiples fois. La même chose sans notation -*do* : +*do*  > in3 start = return start >>= moveKnight >>= moveKnight >>= moveKnight @@ -1241,14 +1249,14 @@ la passer à une fonction avec `>>=` est équivalent à appliquer la fonction normalement à la valeur, mais on le fait quand même pour le style. À présent, écrivons une fonction qui prend deux positions et nous dit si on -peut aller de l'une à l'autre en exactement trois étapes : +peut aller de l'une à l'autre en exactement trois étapes  > canReachIn3 :: KnightPos -> KnightPos -> Bool > canReachIn3 start end = end `elem` in3 start -On génère toutes les solutions possibles pour trois étapes, et on regarde si -la position qu'on cherche est parmi celles-ci. Voyons donc si on peut aller de -`(6, 2)` en `(6, 1]` en trois mouvements : +On génère toutes les solutions possibles pour trois étapes, et on regarde si la +position qu'on cherche est parmi celles-ci. Voyons donc si on peut aller de +`(6, 2)` en `(6, 1]` en trois mouvements  > ghci> (6,2) `canReachIn3` (6,1) > True @@ -1293,7 +1301,7 @@ tiennent. Mais ne vous inquiétez pas, elles ne sont pas compliquées. La première loi dit que si l'on prend une valeur, qu'on la place dans un contexte par défaut via `return` et qu'on la donne à une fonction avec `>>=`, c'est la même chose que de donner directement la valeur à la fonction. Plus -formellement : +formellement  * return x >>= f est égal à f x @@ -1308,7 +1316,7 @@ c'est en effet le cas. Pour la monade `Maybe`, `return` est définie comme `Just`. La monade `Maybe` 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 `return` utilisé avec `Maybe` : +connaît sa valeur. Voici `return` utilisé avec `Maybe`  > ghci> return 3 >>= (\x -> Just (x+100000)) > Just 100003 @@ -1318,7 +1326,7 @@ connaît sa valeur. Voici `return` utilisé avec `Maybe` : Pour la monade des listes, `return` place une valeur dans une liste singleton. L'implémentation de `>>=` 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 : +c'est la même chose que d'appliquer la fonction à la valeur  > ghci> return "WoM" >>= (\x -> [x,x,x]) > ["WoM","WoM","WoM"] @@ -1333,7 +1341,7 @@ loi tienne ainsi pour `IO` également. La deuxième loi dit que si l'on a une valeur monadique et qu'on utilise `>>=` pour la donner à `return`, alors le résultat est la valeur monadique originale. -Formellement : +Formellement  * m >>= return est égal à m @@ -1344,7 +1352,7 @@ valeur monadique. `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, par exemple, pour `Maybe`, qu'elle n'introduit pas d'échec, et pour les listes, qu'elle n'ajoute pas de -non déterminisme. Voici un essaie sur quelques monades : +non déterminisme. Voici un essaie sur quelques monades  > ghci> Just "move on up" >>= (\x -> return x) > Just "move on up" @@ -1354,7 +1362,7 @@ non déterminisme. Voici un essaie sur quelques monades : > Wah! Si l'on regarde de plus près l'exemple des listes, l'implémentation de `>>=` -est : +est  > xs >>= f = concat (map f xs) @@ -1371,7 +1379,7 @@ que les valeurs qu'elle produit fassent des tas d'autres choses. 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é : +Formellement énoncé  * (m >>= f) >>= g est egal à m >>= (\x -> f x >>= g) @@ -1387,7 +1395,7 @@ 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 parche, on avait chaîné plusieurs fonctions qui pouvaient -mener à l'échec : +mener à l'échec  > ghci> return (0,0) >>= landRight 2 >>= landLeft 2 >>= landRight 2 > Just (2,4) @@ -1395,12 +1403,12 @@ mener à l'échec : On commençait avec `Just (0, 0)` et on liait cette valeur à la prochaine fonction monadique, `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 : +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 : +Mais on peut aussi écrire la routine ainsi  > return (0,0) >>= (\x -> > landRight 2 x >>= (\y -> @@ -1416,8 +1424,8 @@ 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, `f` et `g`. -Composer deux fonctions de fait ainsi : +regarder cette loi  considérez la composition de deux fonctions, `f` et +`g`. Composer deux fonctions de fait ainsi  > (.) :: (b -> c) -> (a -> b) -> (a -> c) > f . g = (\x -> f (g x)) @@ -1429,12 +1437,12 @@ le paramètre soit passé entre les deux fonctions. Et si ces deux fonctions fonction de type `a -> m b`, on ne pourrait pas simplement passer son résultat à une fonction de type `b -> m c`, parce que la fonction attend un `b` normal, pas un monadique. On pourrait cependant utiliser `>>=` pour réaliser cela. -Ainsi, en utilisant `>>=`, on peut composer deux fonctions monadiques : +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) -On peut à présent composer deux fonctions monadiques : +On peut à présent composer deux fonctions monadiques  > ghci> let f x = [x,-x] > ghci> let g x = [x*3,x*2] @@ -1462,13 +1470,19 @@ nôtres. diff --git a/recursivite.mkd b/recursivite.mkd index 538254f..848777c 100644 --- a/recursivite.mkd +++ b/recursivite.mkd @@ -3,13 +3,18 @@ @@ -80,10 +85,11 @@ liste ! Et voilà ! Implémentons ça en Haskell. Comme vous le voyez, le filtrage par motif va bien de pair avec la récursivité ! La plupart des langages impératifs ne proposent pas de filtrage par motif, donc vous êtes obligé d'utiliser plein de *if else* pour tester les cas de -base. Ici, on les met simplement dans des motifs. La première condition dit : -si la liste est vide, plante ! Raisonnable, après tout quel est le maximum -d'une liste vide ? Je ne sais pas. Le deuxième motif est aussi un cas de base. -Il dit que pour une liste singleton, il suffit de retourner son unique élément. +base. Ici, on les met simplement dans des motifs. La première condition +dit : si la liste est vide, plante ! Raisonnable, après tout quel est le +maximum d'une liste vide ? Je ne sais pas. Le deuxième motif est aussi un cas +de base. Il dit que pour une liste singleton, il suffit de retourner son +unique élément. Maintenant, le troisième motif est là où se passe l'action. On utilise du filtrage par motif pour séparer la tête et la queue de la liste. C'est un @@ -93,21 +99,21 @@ de la liste. Ensuite, on vérifie si la tête est plus grande que le maximum du reste de la liste. Si c'est le cas, la tête est retournée. Sinon, le maximum du reste de la liste est retourné. -Prenons par exemple une liste de nombres et voyons comment cela marche : `[2, -5, 1]`. Si on appelle `maximum'` dessus, les deux premiers motifs ne vont pas -correspondre. Le troisième marchera, et coupera le `2` du `[5, 1]`. La clause -*where* veut connaître le maximum de `[5, 1]`, donc on suit cette route. Elle -la compare avec succès au troisième motif à nouveau, et `[5, 1]` est coupé en -`5` et `[1]`. À nouveau, la clause *where* veut connaître le maximum de `[1]`. -C'est un cas de base, on retourne donc `1`. Enfin ! En remontant d'une étape, -`5` est comparé au maximum de `[1]` (qui est `1`), et retourne `5`. On sait à -présent que le maximum de `[5, 1]` est `5`. Une étape plus haut, nous +Prenons par exemple une liste de nombres et voyons comment cela marche : +`[2, 5, 1]`. Si on appelle `maximum'` dessus, les deux premiers motifs ne vont +pas correspondre. Le troisième marchera, et coupera le `2` du `[5, 1]`. La +clause *where* veut connaître le maximum de `[5, 1]`, donc on suit cette route. +Elle la compare avec succès au troisième motif à nouveau, et `[5, 1]` est coupé +en `5` et `[1]`. À nouveau, la clause *where* veut connaître le maximum de +`[1]`. C'est un cas de base, on retourne donc `1`. Enfin ! En remontant d'une +étape, `5` est comparé au maximum de `[1]` (qui est `1`), et retourne `5`. On +sait à présent que le maximum de `[5, 1]` est `5`. Une étape plus haut, nous comparions `2` au maximum de `[5, 1]`, qui est `5`, et on choisit donc `5`. Une manière encore plus claire d'écrire cette fonction est d'utiliser `max`. Si vous vous souvenez, `max` est une fonction qui prend deux nombres et retourne le plus grand d'entre eux. Ici, nous pourrions réécrire `maximum'` en utilisant -`max` : +`max` : > maximum' :: (Ord a) => [a] -> a > maximum' [] = error "maximum of empty list" @@ -145,10 +151,10 @@ fonction à atteindre son cas de base.
    -**Note :** `Num` n'est pas une sous-classe d'`Ord`. Cela signifie qu'un nombre -n'a pas forcément besoin d'être ordonnable. C'est pourquoi on doit spécifier à -la fois `Num` et `Ord` en contraintes de classe pour pouvoir faire à la fois -des additions, des soustractions et des comparaisons. +**Note :** `Num` n'est pas une sous-classe d'`Ord`. Cela signifie qu'un +nombre n'a pas forcément besoin d'être ordonnable. C'est pourquoi on doit +spécifier à la fois `Num` et `Ord` en contraintes de classe pour pouvoir faire +à la fois des additions, des soustractions et des comparaisons.
    @@ -157,7 +163,7 @@ liste. Par exemple, `take 3 [5, 4, 3, 2, 1]` retourne `[5, 4, 3]`. Si on essaie de prendre 0 ou moins éléments d'une liste, on récupère une liste vide. Également, si l'ont essaie de prendre quoi que ce soit d'une liste vide, on récupère une liste vide. Remarquez que ce sont nos deux cas de base. Écrivons -tout cela : +tout cela : > take' :: (Num i, Ord i) => i -> [a] -> [a] > take' n _ @@ -258,10 +264,10 @@ que tout le monde le fait déjà pour montrer comme Haskell est élégant. Donc, la signature de type sera `quicksort :: (Ord a) => [a] -> [a]`. Pas de surprise ici. Le cas de base ? La liste vide, bien sûr. Une liste vide triée -est une liste vide. Maintenant, voilà l'algorithme : **une liste triée est une -liste pour laquelle on a pris tous les éléments plus petits que la tête, qu'on -les a triés, et placés à l'avant, puis on a mis la tête, et à la suite on a -placé tous les éléments plus grands que la tête, après les avoir aussi +est une liste vide. Maintenant, voilà l'algorithme : **une liste triée est +une liste pour laquelle on a pris tous les éléments plus petits que la tête, +qu'on les a triés, et placés à l'avant, puis on a mis la tête, et à la suite on +a placé tous les éléments plus grands que la tête, après les avoir aussi triés"**. Remarquez comme on a dit deux fois *trier* dans la définition, donc il y aura probablement deux appels récursifs ! Remarquez aussi qu'on a utilisé le verbe être pour définir l'algorithme, et pas une suite de *faire ceci, puis @@ -293,7 +299,8 @@ nombres à sa gauche sont plus petits que lui, et les 3 à sa droite sont plus grands. Maintenant, si l'on trie récursivement `[1, 4, 3]` et `[9, 6, 7]`, la liste entière est triée ! On les trie à l'aide de la même fonction. Finalement, on va tellement réduire les listes qu'il ne restera que des listes vides, qui -sont triées par définition, puisqu'elles sont vides. Voilà une illustration : +sont triées par définition, puisqu'elles sont vides. Voilà une +illustration : quicksort @@ -329,7 +336,7 @@ de la liste. Et cætera, et cætera… Bien sûr, il y a aussi les cas de base. Généralement, le cas de base est un scénario pour lequel un appel récursif n'a plus de sens. Pour des listes, le plus souvent c'est la liste vide. Si vous traitez des arbres, ce sera -généralement une feuille, i.e. un noeud qui n'a pas d'enfants. +généralement une feuille, i.e. un nœud qui n'a pas d'enfants. C'est la même chose pour traiter des nombres récursivement. Généralement, on a un nombre et une fonction appliquée à une modification de ce nombre. La @@ -354,13 +361,18 @@ récursif. diff --git a/resoudre-des-problemes-fonctionnellement.mkd b/resoudre-des-problemes-fonctionnellement.mkd index 46a50ee..8513ca5 100644 --- a/resoudre-des-problemes-fonctionnellement.mkd +++ b/resoudre-des-problemes-fonctionnellement.mkd @@ -3,13 +3,18 @@ @@ -41,7 +46,7 @@ n'y a pas besoin de parenthèses, et parce que c'est très simple à taper dans une calculatrice. Bien que la plupart des calculatrices modernes utilisent la notation infixe, certaines personnes ne jurent toujours que par leur calculatrice NPI. Voici ce à quoi l'expression infixe précédente ressemble en -NPI : `10 4 3 + 2 * -`. Comment calcule-t-on le résultat de cela ? Eh bien, +NPI  `10 4 3 + 2 * -`. Comment calcule-t-on le résultat de cela ? Eh bien, imaginez une pile. Vous parcourez l'expression de gauche à droite. Chaque fois qu'un nombre est rencontré, vous l'empilez. Dès que vous rencontrez un opérateur, vous retirez les deux nombres en sommet de pile (on dit aussi qu'on @@ -71,12 +76,12 @@ paramètre une chaîne de caractères contenant une expression NPI, comme `"10 4 Quel serait le type d'une telle fonction ? On veut qu'elle prenne une chaîne de caractères en paramètre, et produise un nombre en résultat. Ce sera donc -probablement quelque chose comme `solveRPN :: (Num a) => String a -> a` (NDT : -"RPN" pour "Reverse Polish Notation"). +probablement quelque chose comme `solveRPN :: (Num a) => String a -> a` +(NDT  "RPN" pour "Reverse Polish Notation"). -
    **Astuce :** cela aide beaucoup de réfléchir d'abord à la -déclaration de type d'une fonction avant de s'inquiéter de l'implémentation, et -d'écrire cette déclaration. En Haskell, une déclaration de type nous en dit +
    **Astuce ** cela aide beaucoup de réfléchir d'abord à +la déclaration de type d'une fonction avant de s'inquiéter de l'implémentation, +et d'écrire cette déclaration. En Haskell, une déclaration de type nous en dit beaucoup sur une fonction, grâce au système de types puissant.
    calculatrice @@ -116,7 +121,7 @@ Ensuite, on va utiliser un pli gauche sur la liste et terminer avec une pile à un seul élément, `[-4]`. On sort cet élément de la liste, et c'est notre résultat final ! -Voici donc l'esquisse de notre fonction : +Voici donc l'esquisse de notre fonction  > import Data.List > @@ -137,7 +142,7 @@ nouvelle pile `[3, 4, 10]`. Si la pile est `[4, 10]` et que l'élément est `"*"`, alors elle devra retourner `[40]`. Mais avant cela, transformons notre fonction en [style sans point](fonctions-d-ordre-superieur#composition-de-fonctions), parce qu'elle est -pleine de parenthèses qui m'effraient : +pleine de parenthèses qui m'effraient  > import Data.List > @@ -188,7 +193,7 @@ et `"+"` pour élément. Cela cause le dépilement des deux nombres, qui sont alors sommés, et leur résultat est empilé. La pile finale est `[5]`, qui est le nombre qu'on retourne. -Jouons avec notre fonction : +Jouons avec notre fonction  > ghci> solveRPN "10 4 3 + 2 * -" > -4 @@ -260,14 +265,14 @@ une fois qu'on aura découvert les monades (elles ne sont pas effrayantes, faites-moi confiance !). On pourrait en écrire une dès maintenant, mais ce serait un peu fastidieux parce qu'il faudrait vérifier les valeurs `Nothing` à chaque étape. Toutefois, si vous vous sentez d'humeur pour le défi, vous pouvez -vous lancer ! Indice : vous pouvez utiliser `reads` pour voir si un `read` a -réussi ou non. +vous lancer ! Indice  vous pouvez utiliser `reads` pour voir si un `read` +a réussi ou non.

    D'Heathrow à Londres

    -Notre prochain problème est le suivant : votre avion vient d'atterrir en +Notre prochain problème est le suivant  votre avion vient d'atterrir en Angleterre, et vous louez une voiture. Vous avez un rendez-vous très bientôt, et devez aller de l'aéroport d'Heathrow jusqu'à Londres aussi vite que possible (mais sans vous mettre en danger !). @@ -288,7 +293,7 @@ choisi un autre, il nous faudrait plus longtemps que ça. Notre travail consiste à créer un programme qui prend une entrée représentant un système routier, et affiche le chemin le plus court pour le traverser. Voici -à quoi ressemblera l'entrée dans ce cas : +à quoi ressemblera l'entrée dans ce cas  > 50 > 10 @@ -311,7 +316,7 @@ une dernière route transversale qui prend 0 minute à traverser. Parce qu'une fois arrivé à Londres, ce n'est plus important, on est arrivé. Tout comme on l'a fait en résolvant le problème de la calculatrice NPI, on va -résoudre ce problème en trois étapes : +résoudre ce problème en trois étapes  * Oublier Haskell un instant et penser à la résolution du problème à la main * Penser à la représentation des données en Haskell @@ -332,7 +337,7 @@ fonctionne pour de petites entrées, mais qu'en sera-t-il si notre route a 10 solution est optimale, on pourra simplement se dire qu'on en est plutôt sûr. Ce n'est donc pas une bonne solution. Voici une image simplifiée de notre -système routier : +système routier  routes simplifiées @@ -362,12 +367,11 @@ minutes supplémentaires pour avancer jusqu'à B2 puis traverser ! Évidemment, chemin le plus court jusqu'à A2 est `B, C, A`. De la même façon, le chemin le plus court jusqu'à B2 consiste à aller tout droit depuis A1 et traverser. -
    -**Vous vous demandez peut-être** : mais qu'en est-il d'aller jusqu'à A2 en -traversant à B1 puis en continuant tout droit ? Eh bien, on a déjà couvert la -traversée de B1 à A1 quand on cherchait le meilleur moyen d'aller à A1, donc on -n'a plus besoin de prendre cela en compte à l'étape suivante. -
    +
    **Vous vous demandez peut-être**  mais qu'en est-il +d'aller jusqu'à A2 en traversant à B1 puis en continuant tout droit ? Eh bien, +on a déjà couvert la traversée de B1 à A1 quand on cherchait le meilleur moyen +d'aller à A1, donc on n'a plus besoin de prendre cela en compte à l'étape +suivante.
    À présent qu'on connaît les meilleurs chemins pour aller à A2 et à B2, on peut répéter le processus indéfiniment jusqu'à atteindre l'arrivée. Une fois qu'on @@ -381,13 +385,13 @@ chemins précédents à la première étape, en considérant que ces chemins vid avaient un coût de 0. Voilà un résumé. Pour trouver le meilleur chemin d'Heathrow à Londres, on -procède ainsi : d'abord, on cherche le meilleur chemin jusqu'à la prochaine -intersection de la route A. Il n'y a que deux options : aller directement tout -droit, ou commencer de la route opposée, avancer puis traverser. On se souvient -du meiller chemin et du coût. On utilise la même méthode pour trouver le -meilleur chemin jusqu'à la prochaine intersection de la route B. Ensuite, on se -demande si le chemin pour aller à la prochaine intersection de la route A est -plus court en partant de l'intersection précédente de A et en allant tout +procède ainsi  d'abord, on cherche le meilleur chemin jusqu'à la prochaine +intersection de la route A. Il n'y a que deux options  aller directement +tout droit, ou commencer de la route opposée, avancer puis traverser. On se +souvient du meiller chemin et du coût. On utilise la même méthode pour trouver +le meilleur chemin jusqu'à la prochaine intersection de la route B. Ensuite, on +se demande si le chemin pour aller à la prochaine intersection de la route A +est plus court en partant de l'intersection précédente de A et en allant tout droit, ou en partant de l'intersection précédente de B, en avançant et traversant. On se souvient du chemin le plus court, de son coût, et on fait pareil pour l'intersection opposée. On continue pour chaque section jusqu'à @@ -430,12 +434,12 @@ C'est une manière correcte de représenter le système routier en Haskell et on pourrait certainement résoudre le problème avec, mais on pourrait peut-être trouver quelque chose de plus simple ? Si on revient sur notre solution à la main, on n'a jamais vérifié que les longueurs de trois parties de routes à la -fois : la partie de la route A, sa partie opposée sur la route B, et la partie -C qui relie l'arrivée des deux parties précédentes. Quand on cherchait le plus -court chemin vers A1 et B1, on ne se souciait que des longueurs des trois -premières parties, qui avaient pour longueur 50, 10 et 30. On appellera cela -une section. Ainsi, le système routier qu'on utilise pour l'exemple peut -facilement être représenté par quatre sections : `50, 10, 30`, `5, 90, 20`, +fois  la partie de la route A, sa partie opposée sur la route B, et la +partie C qui relie l'arrivée des deux parties précédentes. Quand on cherchait +le plus court chemin vers A1 et B1, on ne se souciait que des longueurs des +trois premières parties, qui avaient pour longueur 50, 10 et 30. On appellera +cela une section. Ainsi, le système routier qu'on utilise pour l'exemple peut +facilement être représenté par quatre sections  `50, 10, 30`, `5, 90, 20`, `40, 2, 25`, and `10, 8, 0`. Il est toujours bon de garder nos types de données aussi simples que possible, @@ -462,7 +466,7 @@ de données `Section` et `Vector`, on ne peut pas accidentellement sommer un vecteur et une section de système routier.
    -Notre système routier d'Heathrow à Londres peut être représenté ainsi : +Notre système routier d'Heathrow à Londres peut être représenté ainsi  > heathrowToLondon :: RoadSystem > heathrowToLondon = [Section 50 10 30, Section 5 90 20, Section 40 2 25, Section 10 8 0] @@ -473,14 +477,14 @@ fonction qui calcule le plus court chemin pour n'importe quel système routier ? Elle devrait prendre un système routier en paramètre, et retourner un chemin. On représentera un chemin sous forme de liste. Introduisons un type `Label`, qui sera juste une énumération `A`, `B` ou `C`. On fera également un synonyme -de type : `Path`. +de type  `Path`. > data Label = A | B | C deriving (Show) > type Path = [(Label, Int)] Notre fonction, appelons-la `optimalPath`, devrait donc avoir pour déclaration de type `optimalPath :: RoadSystem -> Path`. Si elle est appelée avec le -système routier `heathrowToLondon`, elle doit retourner le chemin suivant : +système routier `heathrowToLondon`, elle doit retourner le chemin suivant  > [(B,10),(C,30),(A,5),(C,20),(B,2),(B,8)] @@ -502,9 +506,9 @@ Path)`. Implémentons directement cette fonction, elle sera forcément utile.
    -**Indice :** elle sera utile parce que `(Path, Path) -> -Section -> (Path, Path)` peut être utilisée comme la fonction binaire du pli -gauche, qui doit avoir pour type `a -> b -> a`. +**Indice ** elle sera utile parce que `(Path, Path) -> Section -> (Path, +Path)` peut être utilisée comme la fonction binaire du pli gauche, qui doit +avoir pour type `a -> b -> a`.
    @@ -573,11 +577,11 @@ consiste à avancer directement sur B.
    -**Astuce d'optimisation :** quand on fait `priceA = sum $ map snd pathA`, on -calcule le prix à partir du chemin à chaque étape de l'algorithme. On pourrait -éviter cela en implémentant `roadStep` comme une fonction ayant pour type -`(Path, Path, Int, Int) -> Section -> (Path, Path, Int, Int)`, où les entiers -réprésenteraient le prix des chemins A et B. +**Astuce d'optimisation ** quand on fait `priceA = sum $ map snd pathA`, +on calcule le prix à partir du chemin à chaque étape de l'algorithme. On +pourrait éviter cela en implémentant `roadStep` comme une fonction ayant pour +type `(Path, Path, Int, Int) -> Section -> (Path, Path, Int, Int)`, où les +entiers réprésenteraient le prix des chemins A et B.
    @@ -658,7 +662,7 @@ notre système routier et a un type correct, c'est-à-dire `RoadSystem` (ou `[Section]`). On appelle `optimalPath` avec ça et on obtient le chemin optimal et son coût dans une représentation textuelle agréable qu'on affiche. -Enregistrons le texte suivant : +Enregistrons le texte suivant  > 50 > 10 @@ -687,13 +691,18 @@ de remplacer `foldl` par `foldl'`, sa version stricte. diff --git a/syntaxe-des-fonctions.mkd b/syntaxe-des-fonctions.mkd index e76596d..b20f293 100644 --- a/syntaxe-des-fonctions.mkd +++ b/syntaxe-des-fonctions.mkd @@ -3,13 +3,18 @@
    @@ -41,10 +46,10 @@ sera utilisé. Ici, le seul moyen pour un nombre de correspondre au premier motif est pour lui d'être le nombre 7. Si ce n'est pas le cas, on passe au second motif, qui accepte tout paramètre et l'attache au nom `x`. Cette fonction aurait pu être implémentée à l'aide d'une construction *if*. Mais, si -l'on voulait que la fonction énonce les nombres entre 1 et 5, et déclare -`"Not between 1 and 5"` pour tout autre nombre ? Sans filtrage par motif, nous +l'on voulait que la fonction énonce les nombres entre 1 et 5, et déclare `"Not +between 1 and 5"` pour tout autre nombre ? Sans filtrage par motif, nous devrions utiliser un arbre de constructions *if then else* plutôt alambiqué. -Cependant, avec le filtrage : +Cependant, avec le filtrage : > sayMe :: (Integral a) => a -> String > sayMe 1 = "One!" @@ -64,7 +69,7 @@ Nous l'avions défini pour un nombre `n` comme `product [1..n]`. On peut également la définir de manière *récursive*, comme en mathématiques. On commence par dire que la factorielle de 0 est 1. Puis, on dit que la factorielle d'un entier positif est égale au produit de cet entier et de la -factorielle de son prédécesseur. En Haskell : +factorielle de son prédécesseur. En Haskell : > factorial :: (Integral a) => a -> a > factorial 0 = 1 @@ -84,14 +89,15 @@ important dans la spécification des motifs, et il est toujours préférable de préciser les motifs les plus spécifiques en premier, et les plus généraux ensuite. -Le filtrage par motif peut aussi échouer. Si l'on définit une fonction : +Le filtrage par motif peut aussi échouer. Si l'on définit une fonction : > charName :: Char -> String > charName 'a' = "Albert" > charName 'b' = "Broseph" > charName 'c' = "Cecil" -et qu'on essaie de l'appeler avec une entrée inattendue, voilà ce qui arrive : +et qu'on essaie de l'appeler avec une entrée inattendue, voilà ce qui +arrive : > ghci> charName 'a' > "Albert" @@ -107,7 +113,7 @@ reste de façon à ce que le programme ne plante pas sur une entrée inattendue. Le filtrage de motifs s'utilise aussi sur les tuples. Comment écrire une fonction qui prend deux vecteurs en 2D (sous forme de paire) et les somme ? Pour sommer les deux vecteurs, on somme séparément les composantes en x et les -composantes en y. Voilà ce que nous aurions fait sans filtrage par motif : +composantes en y. Voilà ce que nous aurions fait sans filtrage par motif : > addVectors :: (Num a) => (a, a) -> (a, a) -> (a, a) > addVectors a b = (fst a + fst b, snd a + snd b) @@ -141,7 +147,7 @@ qu'on se fiche complètement de ce que cette partie est, donc on met juste un `_`. Ce qui me fait penser, vous pouvez aussi utiliser des motifs dans les listes en -compréhension. Voyez par vous-même : +compréhension. Voyez par vous-même : > ghci> let xs = [(1,3), (4,3), (2,4), (5,3), (5,6), (3,1)] > ghci> [a+b | (a,b) <- xs] @@ -157,7 +163,7 @@ ce premier. Un motif de la forme `x:xs` attachera la tête à `x` et le reste à
    -**Note :** Le motif `x:xs` est très utilisé, particulièrement dans les +**Note :** Le motif `x:xs` est très utilisé, particulièrement dans les fonctions récursives. Mais les motifs qui ont un `:` ne peuvent valider que des listes de longueur 1 ou plus. @@ -174,7 +180,7 @@ implémentons notre fonction `head`. > head' [] = error "Can't call head on an empty list, dummy!" > head' (x:_) = x -Vérifions : +Vérifions : > ghci> head' [4,5,6] > 4 @@ -206,7 +212,7 @@ parenthèses). On ne peut pas réécrire `(x:y:_)` de manière analogue car le motif attrape toutes les listes de taille supérieure à 2. Nous avons déjà implémenté `length` à l'aide d'une liste en compréhension. -Utilisons à présent du filtrage par motif et de la récursivité : +Utilisons à présent du filtrage par motif et de la récursivité : > length' :: (Num b) => [a] -> b > length' [] = 0 @@ -231,7 +237,7 @@ l'instant, nous avons `1 + (1 + length' "m")`. `length' "m"` vaut `1 + length' Implémentons `sum`. On sait que la somme d'une liste vide est 0. On l'écrit comme un motif. Ensuite, on sait que la somme d'une liste est égale à la tête -plus la somme du reste. Cela donne : +plus la somme du reste. Cela donne : > sum' :: (Num a) => [a] -> a > sum' [] = 0 @@ -243,7 +249,7 @@ pratique de détruire quelque chose selon un motif tout en gardant une référen Par exemple, le motif `xs@(x:y:ys)`. Ce motif acceptera exactement les mêmes choses que `x:y:ys`, mais vous pourrez facilement désigner la liste complète via `xs` au lieu de réécrire `x:y:ys` dans le corps de la fonction. Exemple -simple : +simple : > capital :: String -> String > capital "" = "Empty string, whoops!" @@ -344,9 +350,9 @@ Beaucoup de débutants font des erreurs de syntaxe en le mettant ici.
    -Un autre exemple simple : implémentons notre propre fonction `max`. Si vous -vous souvenez, elle prend deux choses comparables et retourne la plus grande -d'entre elles. +Un autre exemple simple : implémentons notre propre fonction `max`. Si +vous vous souvenez, elle prend deux choses comparables et retourne la plus +grande d'entre elles. > max' :: (Ord a) => a -> a -> a > max' a b @@ -355,13 +361,13 @@ d'entre elles. Vous pouvez aussi écrire les gardes sur la même ligne, bien que je le déconseille car c'est moins lisible, même pour des fonctions courtes. À titre -d'exemple, on aurait pu écrire : +d'exemple, on aurait pu écrire : > max' :: (Ord a) => a -> a -> a > max' a b | a > b = a | otherwise = b -Erf ! Pas très lisible tout ça ! Passons : implémentons notre propre `compare` -à l'aide de gardes. +Erf ! Pas très lisible tout ça ! Passons : implémentons notre propre +`compare` à l'aide de gardes. > myCompare :: (Ord a) => a -> a -> Ordering > a `myCompare` b @@ -372,15 +378,15 @@ Erf ! Pas très lisible tout ça ! Passons : implémentons notre propre `compare > ghci> 3 `myCompare` 2 > GT -Note : Non seulement pouvons-nous appeler des fonctions de manière infixe avec -des apostrophes renversées, on peut également les définir de cette manière. -Parfois, c'est plus simple à lire comme ça. +Note : Non seulement pouvons-nous appeler des fonctions de manière infixe +avec des apostrophes renversées, on peut également les définir de cette +manière. Parfois, c'est plus simple à lire comme ça.

    Où !?

    -Dans la section précédente, nous définissions le calculateur d'IMC ainsi : +Dans la section précédente, nous définissions le calculateur d'IMC ainsi : > bmiTell :: (RealFloat a) => a -> a -> String > bmiTell weight height @@ -394,7 +400,7 @@ répéter (trois fois) lorsqu'on programme est aussi souhaitable qu'un coup de pied dans la tronche. Plutôt que de répéter la même expression trois fois, il serait idéal de la calculer une fois, de lier le résultat à un nom, et d'utiliser ce nom partout en lieu et place de l'expression. Eh bien, on peut -modifier la fonction ainsi : +modifier la fonction ainsi : > bmiTell :: (RealFloat a) => a -> a -> String > bmiTell weight height @@ -411,7 +417,7 @@ donne l'avantage de ne pas avoir à nous répéter. Si on décide de calculer l' différemment, il suffit de changer la formule à un endroit. Cela facilite aussi la lisibilité en donnant des noms aux choses, et peut rendre votre programme plus rapide puisque la variable `bmi` est ici calculée une unique fois. On -pourrait faire un peu plus de zèle et écrire : +pourrait faire un peu plus de zèle et écrire : > bmiTell :: (RealFloat a) => a -> a -> String > bmiTell weight height @@ -435,7 +441,7 @@ de différentes fonctions. Si vous voulez que plusieurs fonctions aient accès a même nom, il faut le définir globalement. Vous pouvez aussi utiliser les liaisons *where* pour du **filtrage par motif** -! On aurait pu réécrire la section *where* précédente comme : +! On aurait pu réécrire la section *where* précédente comme : > … > where bmi = weight / height ^ 2 @@ -483,7 +489,7 @@ elles-mêmes des expressions, mais très locales, donc elles ne sont pas visible à travers les gardes. Comme toutes les constructions Haskell qui lient des valeurs à des noms, elles supportent le filtrage par motif. Voyons plutôt en action ! Voici comment l'on définit une fonction qui nous donne la surface d'un -cylindre à partir de sa hauteur et de son rayon : +cylindre à partir de sa hauteur et de son rayon : > cylinder :: (RealFloat a) => a -> a -> a > cylinder r h = @@ -511,7 +517,7 @@ Il en va de même des liaisons *let*. > ghci> 4 * (let a = 9 in a + 1) + 2 > 42 -On peut aussi introduire des fonctions à visibilité locale : +On peut aussi introduire des fonctions à visibilité locale : > ghci> [let square x = x * x in (square 5, square 3, square 2)] > [(25,9,4)] @@ -526,7 +532,7 @@ comme séparateurs. Le point-virgule situé après la dernière liaison n'est pas nécessaire, mais peut être écrit à votre convenance. Comme on l'a vu, vous pouvez utiliser du filtrage par motif. C'est très utile pour démanteler rapidement un tuple en ses -composantes et donner un nom à chacune d'elles : +composantes et donner un nom à chacune d'elles : > ghci> (let (a,b,c) = (1,2,3) in a+b+c) * 100 > 600 @@ -544,7 +550,7 @@ seulement qu'il ne filtre pas la liste, mais lie des noms. Les noms définis dans le *let* dans la compréhension sont visibles de la fonction de retour ( celle qui est avant le `|`) et de tous les prédicats et sections qui viennent après la liaison. On pourrait faire une fonction qui ne retourne que les IMC -des personnes obèses : +des personnes obèses : > calcBmis :: (RealFloat a) => [(a, a)] -> [a] > calcBmis xs = [bmi | (w, h) <- xs, let bmi = w / h ^ 2, bmi >= 25.0] @@ -597,7 +603,7 @@ faire du filtrage par motif. Hmmm, prendre une variable, la filtrer par motif, pas déjà fait ça auparavant ? Bien sûr, le filtrage par motif des paramètres d'une définition de fonction ! Eh bien, il s'agit en fait seulement d'un sucre syntaxique pour les expressions *case*. Ainsi, ces deux bouts de code sont -parfaitement interchangeables : +parfaitement interchangeables : > head' :: [a] -> a > head' [] = error "No head for empty lists!" @@ -616,13 +622,13 @@ Comme vous pouvez le voir, la syntaxe des expressions *case* est plutôt simple > … `expression` est testée contre les motifs. L'action de filtrage est comme -attendue : le premier motif qui correspond est utilisé. Si l'on parcourt toute -l'expression *case* sans trouver de motif validé, une erreur d'exécution a -lieu. +attendue : le premier motif qui correspond est utilisé. Si l'on parcourt +toute l'expression *case* sans trouver de motif validé, une erreur d'exécution +a lieu. Alors que le filtrage par motif sur les paramètres d'une fonction ne peut se faire que dans la définition, les expressions *case* peuvent être utilisées à -peu près partout. Par exemple : +peu près partout. Par exemple : > describeList :: [a] -> String > describeList xs = "The list is " ++ case xs of [] -> "empty." @@ -632,7 +638,7 @@ peu près partout. Par exemple : Elles sont utiles pour faire du filtrage par motif sur des choses en plein milieu d'une expression. Du fait que le filtrage par motif dans les définitions de fonction est seulement du sucre syntaxique pour des expressions *case*, on -aurait aussi pu définir : +aurait aussi pu définir : > describeList :: [a] -> String > describeList xs = "The list is " ++ what xs @@ -643,13 +649,18 @@ aurait aussi pu définir :
    diff --git a/types-et-classes-de-types.mkd b/types-et-classes-de-types.mkd index 5abd718..4f71487 100644 --- a/types-et-classes-de-types.mkd +++ b/types-et-classes-de-types.mkd @@ -3,13 +3,17 @@
    @@ -74,7 +78,7 @@ généralement considéré comme une bonne pratique, sauf quand on écrit des fonctions courtes. À présent, nous donnerons aux fonctions que nous déclarerons des déclarations de type. Vous souvenez-vous de la compréhension de liste que nous avions écrite, qui filtre une chaîne de caractères pour ne garder que ceux -en majuscule ? Voici sa déclaration de type : +en majuscule ? Voici sa déclaration de type : > removeNonUppercase :: [Char] -> [Char] > removeNonUppercase st = [ c | c <- st, c `elem` ['A'..'Z']] @@ -87,7 +91,8 @@ type `[Char]` est synonyme de `String`, donc il est plus clair d'écrire fonction de déclaration de type car le compilateur pouvait inférer lui-même que la fonction allait de chaîne de caractères à chaîne de caractères, mais on l'a quand même fait. Mais comment écrire le type d'une fonction qui prend plusieurs -paramètres ? Voici une simple fonction qui prend trois entiers et les somme : +paramètres ? Voici une simple fonction qui prend trois entiers et les +somme : > addThree :: Int -> Int -> Int -> Int > addThree x y z = x + y + z @@ -137,8 +142,8 @@ veux dire, vraiment énormes. `Int` est cependant plus efficace. > ghci> circumference' 4.0 > 25.132741228718345 -`Bool` est un type booléen. Il ne peut prendre que deux valeurs : `True` et -`False`. +`Bool` est un type booléen. Il ne peut prendre que deux valeurs : `True` +et `False`. `Char` représente un caractère. Il est dénoté par des apostrophes. Une liste de caractères est une chaîne de caractères. @@ -146,7 +151,7 @@ caractères est une chaîne de caractères. Les tuples sont des types mais dépendent de la taille et du type de leurs composantes, donc il existe théoriquement une infinité de types de tuples, ce qui fait trop pour être contenu dans ce tutoriel. Remarquez que le tuple vide -`()` est aussi un type, qui ne contient qu'une seule valeur : `()`. +`()` est aussi un type, qui ne contient qu'une seule valeur : `()`.

    Variables de type @@ -208,9 +213,9 @@ Quelle est la signature de type de `==` ?
    -**Note :** l'opérateur d'égalité, `==` est une fonction. De même pour `+`, `*`, -`-`, `/` et quasiment tous les opérateurs. Si une fonction ne contient que des -caractères spéciaux, elle est considérée infixe par défaut. Si nous voulons +**Note :** l'opérateur d'égalité, `==` est une fonction. De même pour `+`, +`*`, `-`, `/` et quasiment tous les opérateurs. Si une fonction ne contient que +des caractères spéciaux, elle est considérée infixe par défaut. Si nous voulons examiner son type, la passer à une autre fonction, ou l'appeler de façon préfixe, nous devons l'entourer de parenthèses. @@ -218,9 +223,9 @@ préfixe, nous devons l'entourer de parenthèses. Intéressant. Nous voyons quelque chose de nouveau ici, le symbole `=>`. Tout ce qui se situe avant le `=>` est appelé une **contrainte de classe**. On peut -lire la déclaration de type précédente ainsi : la fonction d'égalité prend deux -valeurs du même type et retourne un `Bool`. Le type de ces valeurs doit être -membre de la classe `Eq` (ceci est la contrainte de classe). +lire la déclaration de type précédente ainsi : la fonction d'égalité prend +deux valeurs du même type et retourne un `Bool`. Le type de ces valeurs doit +être membre de la classe `Eq` (ceci est la contrainte de classe). La classe de types `Eq` offre une interface pour tester l'égalité. Tout type pour lequel tester l'égalité de deux valeurs a du sens devrait être membre de @@ -232,7 +237,7 @@ La fonction `elem` a pour type `(Eq a) => a -> [a] -> Bool` car elle utilise `==` sur les éléments de la liste pour vérifier si la valeur qu'on cherche est dedans. -Quelques classes de types de base : +Quelques classes de types de base : `Eq` est utilisé pour les types qui supportent un test d'égalité. Ses membres doivent implémenter les fonctions `==` ou `/=` dans leur définition. Donc, si @@ -325,7 +330,7 @@ l'utiliser ensuite, il n'a aucun moyen de savoir de quel type il s'agit. Pour remédier à cela, on peut utiliser des **annotations de type** explicites. Les annotations de type sont un moyen d'exprimer explicitement le type qu'une expression doit avoir. On le fait en ajoutant `::` à la fin de l'expression et -en spécifiant un type. Observez : +en spécifiant un type. Observez : > ghci> read "5" :: Int > 5 @@ -343,15 +348,15 @@ type tout seul. Mais parfois, le compilateur ne sait pas s'il doit plutôt retourner une valeur de type `Int` ou `Float` pour une expression comme `read "5"`. Pour voir le type, Haskell devrait évaluer `read "5"`. Mais puisqu'il est statiquement typé, il doit connaître les types avant de compiler le code (ou, -dans le cas de GHCi, de l'évaluer). Donc nous devons dire à Haskell : "Hé, +dans le cas de GHCi, de l'évaluer). Donc nous devons dire à Haskell : "Hé, cette expression devrait avoir ce type, au cas où tu ne saurais pas !". Les membres d'`Enum` sont des types ordonnés séquentiellement - on peut les énumérer. L'avantage de la classe de types `Enum` est qu'on peut l'utiliser ses types dans les progressions. Elle définit aussi un successeur et un prédécesseur, qu'on peut obtenir à l'aide des fonctions `succ` et `pred`. Les -types membres de cette classe sont : `()`, `Bool`, `Char`, `Ordering`, `Int`, -`Integer`, `Float` et `Double`. +types membres de cette classe sont : `()`, `Bool`, `Char`, `Ordering`, +`Int`, `Integer`, `Float` et `Double`. > ghci> ['a'..'e'] > "abcde" @@ -438,13 +443,17 @@ séparées par des virgules dans les parenthèses.
    diff --git a/zippeurs.mkd b/zippeurs.mkd index aa92f6e..184f9d6 100644 --- a/zippeurs.mkd +++ b/zippeurs.mkd @@ -3,7 +3,10 @@
    • -Et pour quelques monades de plus + +Et pour quelques +monades de plus +
    • [Table des matières](chapitres) @@ -48,7 +51,7 @@ Une petite balade Comme on l'a appris en cours de biologie, il y a beaucoup de sortes différentes d'arbres, alors choisissons une graine qu'on utilisera pour planter le nôtre. -La voici : +La voici  > data Tree a = Empty | Node a (Tree a) (Tree a) deriving (Show) @@ -80,7 +83,7 @@ gratuitement ! > ) > ) -Et voici ce même arbre représenté graphiquement : +Et voici ce même arbre représenté graphiquement  polly dit qu'elle a mal au dos @@ -89,7 +92,7 @@ Remarquez-vous le `W` dans cet arbre ? Disons qu'on souhaite le changer en un `P`. Comment ferions-nous cela ? Eh bien, une façon de faire consisterait à filtrer par motif sur notre arbre jusqu'à ce qu'on trouve l'élément situé en allant d'abord à droite puis à gauche, et qu'on change cet élément. Voici le -code faisant cela : +code faisant cela  > changeToP :: Tree Char -> Tree Char > changeToP (Node x l (Node y (Node _ m n) r)) = Node x l (Node y (Node 'P' m n) r) @@ -105,7 +108,7 @@ sous-arbre qui contenait le `'W'` a maintenant pour racine `'P'`. Y a-t-il un meilleur moyen de faire ceci ? Pourquoi ne pas créer une fonction qui prenne un arbre ainsi qu'une liste de directions ? Les directions seront soit `L` soit `R`, représentant respectivement la gauche et la droite, et on -changera l'élément sur lequel on tombe en suivant ces directions. Voici : +changera l'élément sur lequel on tombe en suivant ces directions. Voici  > data Direction = L | R deriving (Show) > type Directions = [Direction] @@ -124,7 +127,7 @@ signifie qu'on est arrivé à destination, on retourne donc un arbre qui est comme celui en entrée, mais avec `'P'` à sa racine. Pour éviter d'afficher l'arbre entier, créons une fonction qui prend une liste -de directions et nous dit quel est l'élément à la destination : +de directions et nous dit quel est l'élément à la destination  > elemAt :: Directions -> Tree a -> a > elemAt (L:ds) (Node _ l _) = elemAt ds l @@ -134,7 +137,7 @@ de directions et nous dit quel est l'élément à la destination : La fonction est en fait assez similaire à `changeToP`, seulement au lieu de se souvenir des choses en chemin et de reconstruire l'arbre, elle ignore simplement tout sauf sa destination. Ici on change le `'W'` en `'P'` et on -vérifie si le changement a bien eu lieu dans le nouvel arbre : +vérifie si le changement a bien eu lieu dans le nouvel arbre  > ghci> let newTree = changeToP [R,L] freeTree > ghci> elemAt [R,L] newTree @@ -172,26 +175,26 @@ Pour représenter nos miettes de pain, on va aussi utiliser une liste de `Direction` (soit `L`, soit `R`), seulement au lieu de l'appeler `Directions`, on l'appellera `Breadcrumbs`, puisque nos directions seront à présent renversées puisqu'on jette les miettes au fur et à mesure qu'on avance dans -l'arbre : +l'arbre  > type Breadcrumbs = [Direction] Voici une fonction qui prend un arbre et des miettes et se déplace dans le sous-arbre gauche tout en ajoutant `L` dans la liste qui représente nos miettes -de pain : +de pain  > goLeft :: (Tree a, Breadcrumbs) -> (Tree a, Breadcrumbs) > goLeft (Node _ l _, bs) = (l, L:bs) On ignore l'élément à la racine ainsi que le sous-arbre droit, et on retourne juste le sous-arbre gauche ainsi que l'ancienne traînée de miettes, avec `L` -placé à sa tête. Voici une fonction pour aller à droite : +placé à sa tête. Voici une fonction pour aller à droite  > goRight :: (Tree a, Breadcrumbs) -> (Tree a, Breadcrumbs) > goRight (Node _ _ r, bs) = (r, R:bs) Elle fonctionne de façon similaire. Utilisons ces fonctions pour prendre notre -`freeTree` et aller à droite puis à gauche : +`freeTree` et aller à droite puis à gauche  > ghci> goLeft (goRight (freeTree, [])) > (Node 'W' (Node 'C' Empty Empty) (Node 'R' Empty Empty),[L,R]) @@ -204,7 +207,7 @@ miettes de pain sont `[L, R]` parce qu'on est d'abord allé à droite, puis à gauche. Pour rendre plus clair le déplacement dans notre arbre, on peut réutiliser la -fonction `-:` qu'on avait définie ainsi : +fonction `-:` qu'on avait définie ainsi : > x -: f = f x @@ -212,7 +215,7 @@ Ce qui nous permet d'appliquer des fonctions à des valeurs en écrivant d'abord la valeur, puis `-:` et enfin la fonction. Ainsi, au lieu d'écrire `goRight (freeTree, [])`, on peut écrire `(freeTree, []) -: goRight`. En utilisant ceci, on peut réécrire le code ci-dessus de façon à faire mieux apparaître qu'on va -en premier à droite, et ensuite à gauche : +en premier à droite, et ensuite à gauche  > ghci> (freeTree, []) -: goRight -: goLeft > (Node 'W' (Node 'C' Empty Empty) (Node 'R' Empty Empty),[L,R]) @@ -237,7 +240,7 @@ s'il était aussi dans la miette, il y aurait une duplication de l'information. Modifions notre miette de pain afin qu'elle contienne aussi l'information sur tout ce qu'on a ignoré précédemment quand on a choisi d'aller à gauche ou à -droite. Au lieu de `Direction`, définissons un nouveau type de données : +droite. Au lieu de `Direction`, définissons un nouveau type de données  > data Crumb a = LeftCrumb a (Tree a) | RightCrumb a (Tree a) deriving (Show) @@ -260,13 +263,14 @@ trouve le trou. Dans le cas d'un `LeftCrumb`, on sait qu'on est allé à gauche, donc le sous-arbre manquant est le sous-arbre gauche. Changeons également notre synonyme de type `Breadcrumbs` pour refléter le -changement : +changement  > type Breadcrumbs a = [Crumb a] À présent, on doit modifier les fonctions `goLeft` et `goRight` afin qu'elles stockent l'information sur les chemins que l'on n'a pas pris dans nos miettes, -au lieu de tout ignorer comme elles le faisaient auparavant. Voici `goLeft` : +au lieu de tout ignorer comme elles le faisaient auparavant. Voici +`goLeft`  > goLeft :: (Tree a, Breadcrumbs a) -> (Tree a, Breadcrumbs a) > goLeft (Node x l r, bs) = (l, LeftCrumb x r:bs) @@ -282,7 +286,7 @@ pas `Empty`. Un arbre vide n'a pas de sous-arbres, donc si l'on essaie d'aller à gauche dans un arbre vide, une erreur aura lieu parce que le filtrage par motif sur `Node` échouera, et parce qu'on n'a pas mis de motif pour `Empty`. -`goRight` est similaire : +`goRight` est similaire  > goRight :: (Tree a, Breadcrumbs a) -> (Tree a, Breadcrumbs a) > goRight (Node x l r, bs) = (r, RightCrumb x l:bs) @@ -290,7 +294,7 @@ motif sur `Node` échouera, et parce qu'on n'a pas mis de motif pour `Empty`. Précédemment, nous étions capable d'aller à gauche ou à droite. Ce qu'on a gagné, c'est la possibilité de rebrousser chemin, en se souvenant de ce à quoi ressemblaient les nœuds parents et les chemins qu'on n'a pas visités. Voici la -fonction `goUp` : +fonction `goUp`  > goUp :: (Tree a, Breadcrumbs a) -> (Tree a, Breadcrumbs a) > goUp (t, LeftCrumb x r:bs) = (Node x t r, bs) @@ -316,7 +320,7 @@ ses sous-arbres. Ce mécanisme nous permet de facilement nous déplacer à gauch à droite, et vers le haut. Une telle paire contenant une partie focalisée d'une structure de données, et ses alentours, est appelée un zipper, parce que changer de point focal vers le haut et vers le bas fait penser à une braguette -qui monte et descend. Il est donc pratique de créer un synonyme de types : +qui monte et descend. Il est donc pratique de créer un synonyme de types  > type Zipper a = (Tree a, Breadcrumbs a) @@ -329,7 +333,7 @@ donc à `Zipper`. À présent qu'on sait se déplacer de haut en bas, créons une fonction qui modifie l'élément à la racine de l'arbre sur lequel on est actuellement -focalisé : +focalisé  > modify :: (a -> a) -> Zipper a -> Zipper a > modify f (Node x l r, bs) = (Node (f x) l r, bs) @@ -339,21 +343,21 @@ Si l'on se focalise sur un nœud, on modifie son élément racine avec la foncti `f`. Si on se focalise sur un arbre vide, on ne fait rien. Désormais, on peut démarrer avec un arbre, se déplacer où l'on veut, et modifier l'élément à cet endroit, tout en restant focalisé sur cet élément de façon à pouvoir facilement -se déplacer de haut en bas par la suite. Un exemple : +se déplacer de haut en bas par la suite. Un exemple  > ghci> let newFocus = modify (\_ -> 'P') (goRight (goLeft (freeTree,[]))) On va à gauche, puis à droite, et on modifie l'élément racine en le remplaçant -par un `'P'`. C'est encore plus lisible avec `-:` : +par un `'P'`. C'est encore plus lisible avec `-:`  > ghci> let newFocus = (freeTree,[]) -: goLeft -: goRight -: modify (\_ -> 'P') On peut ensuite remonter si l'on veut, et remplacer un élément par un -mystérieux `'X'` : +mystérieux `'X'`  > ghci> let newFocus2 = modify (\_ -> 'X') (goUp newFocus) -Ou avec `-:` : +Ou avec `-:`  > ghci> let newFocus2 = newFocus -: goUp -: modify (\_ -> 'X') @@ -367,7 +371,7 @@ dans le bon sens et sous notre focalisation. Chaque nœud a deux sous-arbres, même si ces sous-arbres sont vides. Ainsi, si l'on se focalise sur un arbre vide, on peut le remplacer par un sous-arbre non vide, en lui attachant un arbre comme feuille. Le code pour faire cela est -simple : +simple  > attach :: Tree a -> Zipper a -> Zipper a > attach t (_, bs) = (t, bs) @@ -376,7 +380,7 @@ On prend un arbre et un zipper, et on retourne un nouveau zipper, dont le point focal est remplacé par l'arbre en question. Non seulement peut-on étendre des arbres ainsi, en remplaçant des sous-arbres vides par d'autres arbres, mais on peut aussi complètement remplacer des sous-arbres existants. Attachons un arbre -tout à gauche de notre `freeTree` : +tout à gauche de notre `freeTree`  > ghci> let farLeft = (freeTree,[]) -: goLeft -: goLeft -: goLeft -: goLeft > ghci> let newFocus = farLeft -: attach (Node 'Z' Empty Empty) @@ -390,7 +394,7 @@ exactement comme `freeTree`, mais avec un `'Z'` en plus tout à gauche. clean!

    Créer une fonction qui remonte tout en haut de notre arbre, peu importe -l'endroit sur lequel on s'est focalisé, est assez facile. La voilà : +l'endroit sur lequel on s'est focalisé, est assez facile. La voilà  > topMost :: Zipper a -> Zipper a > topMost (t,[]) = (t,[]) @@ -415,7 +419,8 @@ sur les sous-listes d'une liste. Après tout, les listes sont un peu comme des arbres, seulement là où le nœud d'un arbre a un élément (ou pas) et plusieurs sous-arbres, un nœud d'une liste n'a qu'un élément et qu'une sous-liste. Quand on avait [implémenté nos propres -listes](creer-nos-propres-types-et-classes-de-types#structures-de-donnees-recursives), on avait défini le type de données ainsi : +listes](creer-nos-propres-types-et-classes-de-types#structures-de-donnees-recursives), +on avait défini le type de données ainsi  > data List a = Empty | Cons a (List a) deriving (Show, Read, Eq, Ord) @@ -452,13 +457,13 @@ précédent était `2`, on peut rebrousser chemin en plaçant simplement cet Puisqu'une miette de pain est juste un élément dans ce cas, on n'a pas vraiment besoin de la placer dans un type de données, comme on l'avait fait avec `Crumb` -pour notre zipper d'arbres : +pour notre zipper d'arbres  > type ListZipper a = ([a],[a]) La première liste représente la liste sur laquelle on se focalise, et la seconde est la liste des miettes. Créons des fonctions pour avancer et reculer -dans des listes : +dans des listes  > goForward :: ListZipper a -> ListZipper a > goForward (x:xs, bs) = (xs, x:bs) @@ -470,7 +475,7 @@ Quand on avance, on se focalise sur la queue de la liste actuelle et on laisse la tête comme miette. Quand on rebrousse chemin, on prend la dernière miette, et on la replace en tête de liste. -Voici les deux fonctions en action : +Voici les deux fonctions en action  > ghci> let xs = [1,2,3,4] > ghci> goForward (xs,[]) @@ -513,7 +518,7 @@ fichiers et peuvent contenir d'autres dossiers. Disons donc qu'un élément d'un système de fichiers est soit un fichier, avec son nom et ses données, soit un dossier, qui a un nom et tout un tas d'autres éléments pouvant être eux-mêmes des fichiers ou des dossiers. Voici le type de données qu'on utilisera et -quelques synonymes de types pour savoir qui est quoi : +quelques synonymes de types pour savoir qui est quoi  > type Name = String > type Data = String @@ -524,7 +529,7 @@ les données qu'il contient. Un dossier vient avec une chaîne de caractères, q est son nom, et une liste d'éléments. Si cette liste est vide, on a un dossier vide. -Voici un dossier avec des fichiers et des sous-dossiers : +Voici un dossier avec des fichiers et des sous-dossiers  > myDisk :: FSItem > myDisk = @@ -580,17 +585,17 @@ avant et celle des éléments venant après, on sait exactement où placer l'élément sur lequel on était focalisé quand on retourne en arrière. Ainsi, on sait où se trouve le trou. -Voici notre type miette pour le système de fichiers : +Voici notre type miette pour le système de fichiers  > data FSCrumb = FSCrumb Name [FSItem] [FSItem] deriving (Show) -Et voici un synonyme de type pour notre zipper : +Et voici un synonyme de type pour notre zipper  > type FSZipper = (FSItem, [FSCrumb]) Remonter d'un cran dans la hiérarchie est très simple. On prend simplement la dernière miette et on assemble le nouveau point focal à partir du précédent et -de la miette. Ainsi : +de la miette. Ainsi  > fsUp :: FSZipper -> FSZipper > fsUp (item, FSCrumb name ls rs:bs) = (Folder name (ls ++ [item] ++ rs), bs) @@ -605,7 +610,7 @@ miette qu'on laissera devra inclure le nom `"root"` ainsi que les éléments précédant `"dijon_poupon.doc"` et ceux qui viennent après. Voici une fonction qui, étant donné un nom, se focalise sur un fichier ou un -dossier situé dans le dossier courant : +dossier situé dans le dossier courant  > import Data.List (break) > @@ -654,7 +659,7 @@ Partons de la racine et dirigeons-nous vers le fichier `"skull_man(scary).bmp"` `newFocus` est à présent un zipper qui est focalisé sur le fichier `"skull_man(scary).bmp"`. Récupérons le premier élément du zipper (le point -focal lui-même) et vérifions si c'est bien vrai : +focal lui-même) et vérifions si c'est bien vrai  > ghci> fst newFocus > File "skull_man(scary).bmp" "Yikes!" @@ -669,20 +674,20 @@ Remontons d'un cran pour nous focaliser sur son voisin `"watermelon_smash.gif"`

    Manipuler notre système de fichiers

    À présent qu'on sait naviguer dans notre système de fichiers, le manipuler est -très simple. Voici une fonction qui renomme le fichier ou dossier courant : +très simple. Voici une fonction qui renomme le fichier ou dossier courant  > fsRename :: Name -> FSZipper -> FSZipper > fsRename newName (Folder name items, bs) = (Folder newName items, bs) > fsRename newName (File name dat, bs) = (File newName dat, bs) -On peut maintenant renommer `"pics"` en `"cspi"` : +On peut maintenant renommer `"pics"` en `"cspi"`  > ghci> let newFocus = (myDisk,[]) -: fsTo "pics" -: fsRename "cspi" -: fsUp On est descendu jusqu'au dossier `"pics"`, on l'a renommé, et on est remonté. Pourquoi pas une fonction qui prend un nouvel élément, et l'ajoute au dossier -courant ? Admirez : +courant ? Admirez  > fsNewFile :: FSItem -> FSZipper -> FSZipper > fsNewFile item (Folder folderName items, bs) = @@ -691,7 +696,7 @@ courant ? Admirez : C'était du gâteau. Remarquez que ceci planterait si l'on essayait d'ajouter un élément alors que notre point focal était un fichier plutôt qu'un dossier. -Ajoutons un fichier au dossier `"pics"` et retournons ensuite à la racine : +Ajoutons un fichier au dossier `"pics"` et retournons ensuite à la racine  > ghci> let newFocus = (myDisk,[]) -: fsTo "pics" -: fsNewFile (File "heh.jpg" "lol") -: fsUp @@ -716,7 +721,7 @@ Jusqu'ici, lorsqu'on traversait nos structures de données, qu'elles soient des arbres binaires, des listes ou des systèmes de fichiers, on ne se préoccupait pas trop d'avancer un pas trop loin et de tomber dans le vide. Par exemple, notre fonction `goLeft` prend un zipper et un arbre binaire et déplace le point -focal vers son sous-arbre gauche : +focal vers son sous-arbre gauche  > goLeft :: Zipper a -> Zipper a > goLeft (Node x l r, bs) = (l, LeftCrumb x r:bs) @@ -745,7 +750,7 @@ nos mouvements. On va prendre les fonctions qui marchent sur notre zipper d'arbres binaires et les rendre monadiques. D'abord, occupons-nous des échecs possibles de `goLeft` et `goRight`. Jusqu'ici, l'échec de fonctions pouvant échouer était toujours reflété dans leur résultat, il en va de même ici. Voici -`goLeft` et `goRight` pouvant échouer : +`goLeft` et `goRight` pouvant échouer  > goLeft :: Zipper a -> Maybe (Zipper a) > goLeft (Node x l r, bs) = Just (l, LeftCrumb x r:bs) @@ -766,13 +771,13 @@ obtient `Nothing` ! Ça a l'air bon ! Et pour rebrousser chemin ? Le problème avait lieu lorsqu'on essayait de remonter alors qu'on n'avait plus de miettes, ce qui signifiait qu'on était déjà à la racine de notre arbre. Voici la fonction `goUp` qui lance -une erreur si l'on ne reste pas dans les limites de notre arbre : +une erreur si l'on ne reste pas dans les limites de notre arbre  > goUp :: Zipper a -> Zipper a > goUp (t, LeftCrumb x r:bs) = (Node x t r, bs) > goUp (t, RightCrumb x l:bs) = (Node x l t, bs) -Modifions-là pour échouer gracieusement : +Modifions-là pour échouer gracieusement  > goUp :: Zipper a -> Maybe (Zipper a) > goUp (t, LeftCrumb x r:bs) = Just (Node x t r, bs) @@ -782,8 +787,8 @@ Modifions-là pour échouer gracieusement : Si l'on a des miettes, tout va bien et on retourne un nouveau point focal réussi, mais si ce n'est pas le cas, on retourne un échec. -Auparavant, ces fonctions prenaient des zippers et retournaient des zippers, -ce qui signifiant qu'on pouvais les chaîner ainsi pour se balader : +Auparavant, ces fonctions prenaient des zippers et retournaient des zippers, ce +qui signifiant qu'on pouvais les chaîner ainsi pour se balader  > gchi> let newFocus = (freeTree,[]) -: goLeft -: goRight @@ -803,7 +808,7 @@ qui prend une valeur dans un contexte (dans notre cas, un `Maybe (Zipper a)`, qui a un contexte d'échec potentiel) et la donne à une fonction en s'assurant que le contexte est bien géré. Ainsi, tout comme avec notre funambule, on va échanger nos `-:` contre des `>>=`. Très bien, on peut à nouveau chaîner nos -fonctions ! Observez : +fonctions ! Observez  > ghci> let coolTree = Node 1 Empty (Node 3 Empty Empty) > ghci> return (coolTree,[]) >>= goRight @@ -817,7 +822,7 @@ On a utilisé `return` pour placer un zipper dans un `Just`, puis utilisé `>>=` pour donner cela à notre fonction `goRight`. D'abord, on crée un arbre qui a un sous-arbre gauche vide, et un sous-arbre droit avec deux sous-arbres. Quand on essaie d'aller à droite une fois, c'est un succès, parce que l'opération est -logique. Aller deux fois à droite est aussi correct ; on se retrouve avec un +logique. Aller deux fois à droite est aussi correct, on se retrouve avec un sous-arbre vide en point focal. Mais aller à droite trois fois n'a pas de sens, parce qu'on ne peut pas aller à droite dans un sous-arbre vide, c'est pourquoi le résultat est `Nothing`. @@ -833,7 +838,10 @@ gracieusement en utilisant la monade `Maybe`.