Le système de typage associe un genre (kind) à tout type et toute classe.
Dans GHCi, on peut l’obtenir avec la commande :kind.
Int, Int -> Int ou [Int] sont de genre *.Maybe ou [] (liste) sont de genre * -> *.* -> * -> *, (* -> *) -> * ou * -> Constraint.Foncteur, applicatif et monade sont des types de genre * -> * et
pour lesquels sont définies des opérations qui respectent certaines lois.
Une monade est un foncteur applicatif qui est lui-même un foncteur. Autrement dit, la monade enrichit l’ensemble des opérations que définit le foncteur applicatif, lui-même plus grand que celui que définit le foncteur.
Intuitivement, un foncteur est un conteneur pour lequel on peut appliquer une fonction à son contenu.
Un exemple typique est la liste, dont la fonction map permet d’appliquer une fonction à tous ses éléments.
Prelude> map (*2) [1,2,3]
[2,4,6]
Functor et fmap§Le concept de foncteur généralise map à toutes les instances de la classe Functor,
qui exige la définition d’une fonction fmap :
class Functor f where
fmap :: (a -> b) -> f a -> f b
Cette fonction doit être implémentée de façon à ce que ces deux lois soient respectées :
fmap id == id
fmap (f . g) == fmap f . fmap g
Une liste est un foncteur dont la fonction fmap correspond exactement à map :
Prelude> fmap (*2) [1,2,3]
[2,4,6]
Prelude> fmap (*2) []
[]
Maybe§Maybe est défini dans le Prélude comme :
data Maybe a = Just a | Nothing
On peut l’utiliser ainsi :
minimum' :: (Ord a) => [a] -> Maybe a
minimum' [] = Nothing
minimum' all@(x:xs) = Just (minimum all)
*Main> minimum' [1,3,-2,5]
Just (-2)
*Main> minimum' []
Nothing
Maybe est un foncteur§Le typage nous empêche d’utiliser directement la valeur.
*Main> 2 * minimum' [1,3,-2,5]
<interactive>:16:1:
Non type-variable argument in the constraint: Num (Maybe a)
(Use FlexibleContexts to permit this)
When checking that ‘it’ has the inferred type
it :: forall a. (Num a, Num (Maybe a), Ord a) => Maybe a
On peut néanmoins manipuler ces valeurs grâce à fmap :
*Main> fmap (*2) (Just 5)
Just 10
*Main> fmap (*2) Nothing
Nothing
pure et opérateur <*>§Intuitivement, les foncteurs applicatifs rendent possible non seulement l’application d’une fonction à un paramètre sur leur contenu, mais aussi l’application d’une fonction à plusieurs paramètres sur autant de foncteurs applicatifs.
Prelude> (*) 2 5
10
Prelude> pure (*) <*> (Just 2) <*> (Just 5)
Just 10
Comme cet exemple le suggère, Maybe est un foncteur applicatif.
Applicative§Ce concept est implémenté par la classe Applicative qui hérite de Functor.
Un type qui instancie Applicative doit donc instancier également Functor,
et définir la fonction pure, ainsi que l’opérateur <*> :
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
Comme pour fmap, ces fonctions doivent être implémentées de façon à ce que
plusieurs lois soient respectées, notamment :
pure f <*> x = fmap f x
Une liste est un foncteur applicatif pour lequel pure construit une liste contenant l’élément donné
en argument et l’opérateur <*> prend une liste de fonctions et une liste de valeurs et construit une liste
où chacune des fonctions est appliquée sur chacune des valeurs.
Prelude> [(*2), (+1)] <*> [1,2,3]
[2,4,6,2,3,4]
Prelude> pure (*) <*> [2, 1] <*> [1,2,3]
[2,4,6,1,2,3]
Prelude> pure (*)
[(*)]
Prelude> [(*)] <*> [2,1]
[(*2),(*1)]
Prelude> [(*2), (*1)] <*> [1,2,3]
[2,4,6,1,2,3]
Intuitivement, les monades sont des conteneurs, comme les foncteurs, qui offrent une manière d’enchaîner des fonctions encapsulant leur valeur de retour dans une monade.
minimum' :: (Ord a) => [a] -> Maybe a
minimum' [] = Nothing
minimum' all@(x:xs) = Just (minimum all)
*Main> minimum' [1,3,-2,5]
Just (-2)
*Main> Just [1,3,-2,5] >>= minimum'
Just (-2)
*Main> Nothing >>= minimum'
Nothing
Comme le suggère cet exemple Maybe est une monade.
Monad§Ce concept est implémenté par la classe Monad qui hérite de Applicative.
Un type qui instancie Monad doit donc instancier également Applicative,
et définir l’opérateur >>= (bind), le reste étant implémenté par défaut.
class Applicative m => Monad m where
return :: a -> f a
return = pure
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
m >> n = m >>= (\_ -> n)
L’opérateur >>= prend une monade m a, contenant une valeur de type a,
applique une fonction (a -> m b) sur celle-ci et retourne la monade
qui en résulte m b. L’opérateur >> est une version particulière où la valeur
de la première monade n’est pas transmise à la seconde.
La sémantique de ces opérateurs doit respecter les lois monadiques :
return a >>= k = k a
m >>= return = m
xs >>= return . f = fmap f xs
m >>= (\x -> k x >>= h) = (m >>= k) >>= h
Que donnent les expressions suivantes ?
return 'a' :: [Char] et return 4 :: Maybe Int"abc" >>= (\x -> [x,x]) et "" >>= (\x -> [x,x])[(4,'a'),(2,'b'),(1,'a')] >>= (\(nb,elt) -> take nb (repeat elt))Just 5 >>= (\x -> Just (x*2))Nothing >>= (\x -> Just (x*2))Just 5 >>= (\x -> return (x*2))Just 5 >>= return . (*2)Just 5 >> Just 6 et Nothing >> Just 6[2,5,0] >> "bc" et "bc" >> [2,5,0]do§Il y a une notation qui évite l’usage explicite de >> et >>= :
do e1 ; e2 = e1 >> e2
do p <- e1; e2 = e1 >>= \p -> e2
Par exemple :
*Main> Just [1,3,-2,5] >>= minimum' >>= (\x -> return (x-10))
Just (-12)
*Main> do lst<- (Just [1,3,-2,5]); min<- minimum' lst; return (min-10)
Just (-12)
Notez qu’en général on passe à la ligne plutôt qu’utiliser un point-virgule.
do§Que donnent les expressions suivantes ?
do x <- "abc"; [x,x]do (nb,elt) <- [(4,'a'),(2,'b')]; take nb (repeat elt))do x <- Just 5; Just (x*2)do x <- Nothing; Just (x*2)do x <- Just 5; return (x*2)do Just 5; Just 6do [2,5,0]; "bc"do [2,5,0]; "bc"; [0]String et IO§show, read§Les fonctions show and read permettent de transformer
une valeur d’une instance des classes Show and Read
en String et inversement.
*Main> show (Carte Trois Carreau)
"Carte Trois Carreau"
*Main> read "Carte Deux Pique" :: Carte
Carte Deux Pique
Notez qu’on indique le type de la valeur que doit retourner read
car le système de type n’a aucun moyen de le deviner.
*Main> read "Carte Deux Pique"
*** Exception: Prelude.read: no parse
*Main> read "n'importe quoi" :: Carte
*** Exception: Prelude.read: no parse
getLine, putStrLn§Tout action d’entrée-sortie retourne une valeur qui est étiquettée par le type monadique IO.
Par exemple, getLine réalise une action de lecture et retourne une chaîne de caractère.
getLine :: IO String
Les actions qui ne retournent aucune valeur utile prennent le type IO ().
Par exemple, putStrLn prend une chaîne pour l’afficher mais ne retourne rien.
putStrLn :: String -> IO ()
Remarque : print = putStrLn . show.
Les actions sont séquencées avec l’opérateur >>=.
>>= :: IO a -> (a -> IO b) -> IO b
Ce programme réalise deux actions d’entrée-sortie à la suite : il lit une chaîne, puis l’affiche sur la sortie standard.
main :: IO ()
main = getLine >>= putStrLn
do§Le mot-clé do introduit une séquence d’instructions
implicitement enchaînées.
Ces instructions sont soit des actions, soit des définitions locales
avec des let-statement où des <- pour récupérer les valeurs provenant
d’entrées-sorties.
Ce programme est l’équivalent du précédent :
main :: IO ()
main = do l <- getLine
putStrLn l
main :: IO ()
main =
putStrLn "Entrez un texte :"
>> getLine
>>= putStrLn . reverse
main :: IO ()
main = do
putStrLn "Entrez un texte :"
line <- getLine
(putStrLn . reverse) line
let§main :: IO ()
main =
putStrLn "Entrez un texte :"
>> getLine
>>= (\line -> let rline = reverse line in putStrLn rline)
main :: IO ()
main = do
putStrLn "Entrez un texte :"
line <- getLine
let rline = reverse line
putStrLn rline
return§getName :: IO (Maybe String)
getName =
putStrLn "Name : "
>> getLine
>>= (\name -> let maybeName = if null name
then Nothing
else Just name
in return maybeName )
getName :: IO (Maybe String)
getName = do
putStrLn "Name : "
name <- getLine
let maybeName = if null name then Nothing else Just name
return maybeName
main :: IO ()
main = getName >>= print
ghc fichier.hs -o executable produit
directement un exécutable : ./executable, à partir
du moment où fichier.hs contient un main.-outputdir build
pour mettre les fichiers intermédiaires dans un répertoire séparé build
(voir man ghc ou ghc --help).A partir d’un type Carte modélisant une carte à jouer,
définissez la fonction :
jeu :: Carte -> IO ()
appelée ci-dessous :
main = do
let laCarte = Carte As Trefle
putStr "indice: "
print laCarte
-- jeu de devinette
jeu laCarte
L’utilisateur tape le nom d’une carte. Si c’est celle mémorisée dans le programme, il a gagné, sinon il a le droit de proposer une nouvelle carte.
Maybe, [], IO.read, show, getLine, putStrLn.mainPrelude> return 'a' :: [Char]
"a"
Prelude> return 4 :: Maybe Int
Just 4
Prelude> "abc" >>= (\x -> [x,x])
"aabbcc"
Prelude> "" >>= (\x -> [x,x])
""
Prelude> [(4,'a'),(2,'b'),(1,'a')] >>=
(\(nb,elt) -> take nb (repeat elt))
"aaaabba"
Prelude> Just 5 >>= (\x -> Just (x*2))
Just 10
Prelude> Nothing >>= (\x -> Just (x*2))
Nothing
Prelude> Just 5 >>= (\x -> return (x*2))
Just 10
Prelude> Just 5 >>= return . (*2))
Just 10
Prelude> Just 5 >> Just 6
Just 6
Prelude> Nothing >> Just 6
Nothing
Prelude> [2,5,0] >> "bc"
"bcbcbc"
Prelude> "bc" >> [2,5,0]
[2,5,0,2,5,0]
do§Prelude> do x <- "abc"; [x,x]
"aabbcc"
Prelude> do (nb,elt) <- [(4,'a'),(2,'b')]; take nb (repeat elt)
"aaaabb"
Prelude> do x <- Just 5; Just (x*2)
Just 10
Prelude> do x <- Nothing; Just (x*2)
Nothing
Prelude> do x <- Just 5; return (x*2)
Just 10
Prelude> do Just 5; Just 6
Just 6
Prelude> do [2,5,0]; "bc"
"bcbcbc"
Prelude> do [2,5,0]; "bc"; [0]
[0,0,0,0,0,0]
data CouleurCarte = Trefle | Carreau | Coeur | Pique
deriving (Eq, Ord, Enum, Read, Show)
data ValeurCarte = Deux | Trois | Quatre | Cinq | Six | Sept |
Huit | Neuf | Dix | Valet | Dame | Roi | As
deriving (Eq, Ord, Enum, Read, Show)
data Carte = Carte ValeurCarte CouleurCarte
deriving (Eq, Read, Show)
jeu :: Carte -> IO ()
jeu carteATrouver =
do
line <- getLine
if (read line) == carteATrouver
then putStrLn "Vous avez gagne!"
else do
putStrLn "Non, reessayez!"
jeu carteATrouver
main = do
let laCarte = Carte As Trefle
putStr "indice: "
print laCarte
-- jeu de devinette
jeu laCarte