Webographie :
ghci, occulte les histoires d’IO..hs avec runhaskell.Une sortie
main :: IO ()
main = affiche "Hello World!"
affiche :: String -> IO ()
affiche = putStrLn
main est le point d’entrée des programmes,->, donc n’accepte pas de paramètre (constante ?),main peut être rappelée récursivement.IO est un constructeur de type, bientôt détaillé en profondeur,main est une action, travaille dans le cadre d’une entrée/sortie.() est le type vide, souvent appelé unit,main ne ramène pas de valeur, n’a pas de compte à rendre.Une entrée puis une sortie, première version.
main :: IO ()
main = do
input <- demander
affiche (a_lenvers input)
demander :: IO String
demander = getLine
affiche :: String -> IO ()
affiche = putStrLn
-- a_lenvers est pure (non marquée IO)
a_lenvers :: [a] -> [a]
a_lenvers = reverse
do (bientôt détaillée en profondeur aussi),<- stocke le résultat d’une action dans un nom (bind),demander, qui va dans le monde extérieur (IO),String).Une entrée puis une sortie, deuxième version.
main :: IO ()
main = do
reversed_input <- (fmapIO a_lenvers demander)
affiche reversed_input
fmapIO :: (a -> b) -> (IO a) -> (IO b)
fmapIO = fmap -- IO est un foncteur
demander :: IO String
demander = getLine
affiche :: String -> IO ()
affiche = putStrLn
a_lenvers :: String -> String
a_lenvers = reverse
Une entrée puis une sortie, troisième version.
import Control.Applicative
main :: IO () -- IO est aussi un foncteur applicatif
main = do
reversed_input <- ((pure a_lenvers) <*> demander)
-- reversed_input <- (a_lenvers <$> demander)
affiche reversed_input
demander :: IO String
demander = getLine
affiche :: String -> IO ()
affiche = putStrLn
a_lenvers :: String -> String
a_lenvers = reverse
Note : ($) :: (a -> b) -> a -> b est l’opérateur d’application.
f $ x signifie f x (détails plus loin).
Une entrée puis une sortie, quatrième version.
main :: IO ()
main = interaction a_lenvers
interaction :: (String -> String) -> IO ()
interaction = interact -- très pratique pour stdin -> stdout
a_lenvers :: String -> String
a_lenvers = reverse
Utilisation :
CTRL + D en fin d’entrée,echo bonjour | runhaskell reverse_interact.hs.Une entrée puis une sortie, quelques variations en point-free.
main = interact reverse
main = interact unwords.(map (++ " !!! ")).words.(map toUpper)
do.class Functor f where
fmap :: (a -> b) -> f a -> f b
* -> *.fmap :: (a -> b) -> f a -> f b nous rappelle map :: (a -> b) -> [a] -> [b].
En fait on a même :
instance Functor [] where
fmap = map
Une liste est une boîte contenant une ou plusieurs valeurs, voire aucune.
Plus précisément, une liste peut être vue comme une valeur non déterministe, pouvant prendre aucune, une, ou plusieurs valeurs.
Rappel : data Maybe a = Nothing | Just a
instance Functor Maybe where
fmap f (Just x) = Just (f x)
fmap f Nothing = Nothing
On a ici une boîte contenant une valeur ou ne contenant rien.
Plus précisément, le calcul est dans un contexte de réussite.
Il peut aboutir ou non, être valide ou non…
instance Functor ((->) r) where
fmap f g = (\x -> f (g x))
Le type de fmap devient ici :
fmap :: (a -> b) -> ((->) r a) -> ((->) r b) c’est-à-dire en infixe :
fmap :: (a -> b) -> (r -> a) -> (r -> b).
instance Functor ((->) r) where
fmap = (.)
Les valeurs cachées dans la boîte sont les images de la fonction.
Fait appel à >>= et return, que nous détaillerons bientôt.
instance Functor IO where
fmap f ioa = ioa >>= (\a -> return (f a))
-- fmap f ioa = ioa >>= (return . f)
Voire, avec do :
instance Functor IO where
fmap f ioa = do
value <- ioa
return (f value)
Le contexte de calcul est une entrée ou une sortie (ou les deux).
On récupère la valeur de l’action, on lui applique f, puis on remet le
résultat dans un contexte d’entrée/sortie (destruction, application,
construction).
-> étant associative à droite, on a aussi :
fmap :: (a -> b) -> (f a -> f b)
c’est-à-dire que si on ne donne à fmap qu’un paramètre,
on obtient une fonction qui « travaille dans le foncteur ».
On dit qu’on lifte la fonction, qu’on l’élève.
Un exemple de la fonction double, liftée dans les listes :
ghci> let f = (* 2)
ghci> let f' = fmap f :: [Integer] -> [Integer]
ghci> f' [0..5]
[0,2,4,6,8,10]
Lois des foncteurs, non imposées en Haskell :
fmap id = id
fmap (f . g) = fmap f . fmap g
class (Functor f) => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
<*> est le fmap des foncteurs applicatifs,<*> est aussi « dans le foncteur ».maper sur plusieurs paramètres.Rappel :
($) :: (a -> b) -> a -> b
f $ x signifie f x,f $ g $ h x qui signifie f (g (h x))map ($ 0) xszipWith ($) fs xs(<$>) :: (Functor f) => (a -> b) -> f a -> f b
f <$> x = fmap f x -- pas le même f que ci-dessusinstance Applicative Maybe where
pure = Just
Nothing <*> _ = Nothing
(Just f) <*> something = fmap f something
instance Applicative [] where
pure x = [x]
fs <*> xs = [f x | f <- fs, x <- xs]
instance Applicative ((->) r) where
pure x = (\_ -> x)
f <*> g = \x -> f x (g x)
instance Applicative IO where
pure = return
a <*> b = do
f <- a
x <- b
return (f x)
-- lift d’une fonction à deux paramètres
liftA2 :: (Applicative f) => (a -> b -> c) -> f a -> f b -> f c
liftA2 f a b = f <$> a <*> b
-- rassembler plusieurs contextes en un
sequenceA :: (Applicative f) => [f a] -> f [a]
sequenceA [] = pure []
sequenceA (x:xs) = (:) <$> x <*> sequenceA xs
-- idem avec un fold
sequenceA :: (Applicative f) => [f a] -> f [a]
sequenceA = foldr (liftA2 (:)) (pure [])
ghci> sequenceA [Just 3, Just 2, Just 1]
Just [3,2,1]
ghci> sequenceA [Just 3, Nothing, Just 1]
Nothing
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]]
ghci> sequenceA [[1,2,3],[4,5,6],[3,4,4],[]]
[]
ghci> sequenceA [(+3),(+2),(+1)] 3
[6,5,4]
Dans le foncteur IO, sequenceA est sequence.
ghci> :t sequence
sequence :: Monad m => [m a] -> m [a]
ghci> sequence [getLine, getLine, getLine, getLine]
bonjour
tout
le
monde
["bonjour","tout","le","monde"]
pure id <*> v = v
pure (.) <*> u <*> v <*> w = u <*> (v <*> w)
pure f <*> pure x = pure (f x)
u <*> pure y = pure ($ y) <*> u
class Monad m where
return :: a -> m a
-- bind
(>>=) :: m a -> (a -> m b) -> m b
-- then
(>>) :: m a -> m b -> m b
x >> y = x >>= \_ -> y
fail :: String -> m a
fail msg = error msg
instance Monad Maybe where
return x = Just x
Nothing >>= f = Nothing
Just x >>= f = f x
fail _ = Nothing
Un exemple :
ghci> let f n = if n==0 then Nothing else Just (n-1)
ghci> f 18
Just 17
ghci> f 1
Just 0
ghci> f 0
Nothing
ghci> (Just 2) >>= f >>= f
Just 0
ghci> (Just 2) >>= f >>= f >>= f
Nothing
Un exemple illustrant la notation do :
ghci> let positive_sum x y = if x+y > 0 then Just (x+y) else Nothing
ghci> :t positive_sum
positive_sum :: (Ord a, Num a) => a -> a -> Maybe a
ghci> :t positive_sum 0
positive_sum 0 :: (Ord a, Num a) => a -> Maybe a
ghci> :t \y -> positive_sum 0 y
\y -> positive_sum 0 y :: (Ord a, Num a) => a -> Maybe a
ghci> positive_sum 1 2
Just 3
ghci> positive_sum 1 (-2)
Nothing
Un exemple illustrant la notation do :
ghci> Just 1 >>= (\x -> positive_sum x 2)
Just 3
ghci> Just 1 >>= (\x -> (Just 2 >>= (\y -> positive_sum x y)))
Just 3
ghci> do x <- Just 1; y <- Just 2; positive_sum x y
Just 3
ghci> do x <- Just 1; y <- Just (-2); positive_sum x y
Nothing
La notation do (dont fait partie <-) n’est que du sucre syntactique pour
>>=.
instance Monad [] where
return x = [x]
xs >>= f = concat (map f xs)
fail _ = []
Un exemple :
ghci> let plus2 x = [x+2]
ghci> :t plus2
plus2 :: Num t => t -> [t]
ghci> [1, 2] >>= plus2
[3,4]
ghci> [1, 2] >>= plus2 >>= plus2
[5,6]
ghci> let plus1_plus2 x = [x+1, x+2]
ghci> [1, 2] >>= plus1_plus2
[2,3,3,4]
ghci> [1, 2] >>= plus1_plus2 >>= plus1_plus2
[3,4,4,5,4,5,5,6]
Les listes en compréhension ne sont que du sucre syntactique autour de la notion de MonadPlus (combinaison de monade et de monoïde).
class Monad m => MonadPlus m where
mzero :: m a
mplus :: m a -> m a -> m a
instance MonadPlus [] where
mzero = []
mplus = (++)
guard :: (MonadPlus m) => Bool -> m ()
guard True = return ()
guard False = mzero
-- lois
mzero `mplus` m = m
m `mplus` mzero = m
m `mplus` (n `mplus` o) = (m `mplus` n) `mplus` o
...
multiplyTo :: Int -> [(Int, Int)]
multiplyTo n = do
x <- [1..n]
y <- [x..n]
guarded (x * y == n) $ return (x, y)
guarded :: Bool –> [ a ] –> [ a ]
guarded True xs = xs
guarded False _ = []
-- Control.Monad.Instances
instance Monad ((->) r) where
return x = \_ -> x
h >>= f = \w -> f (h w) w
C’est la monade Reader, qui injecte la même valeur dans toutes
les fonctions qu’elle assemble.
IO ne peut pas être implémentée en Haskell pur.
domain :: IO ()
main = demander >>= (\input -> affiche (a_lenvers input))
main :: IO ()
main = demander >>= \input ->
affiche (a_lenvers input)
main :: IO ()
main = do
input <- demander
affiche (a_lenvers input)
domain = demander >>= (\input1 ->
demander >>= (\input2 ->
affiche (input2 ++ input1)))
main = demander >>= \input1 ->
demander >>= \input2 ->
affiche (input2 ++ input1)
main = do
input1 <- demander
input2 <- demander
affiche (input2 ++ input1)
dodo assemble les actions.<-do ne peut être bindée,do.returnreturn impératif !<- et return sont réciproques l’une de l’autre.main),main (et main seul) démarre l'exécution en cascade des do),return a >>= f = f a
m >>= return = m
(m >>= f) >>= g = m >>= (\x -> f x >>= g)
-- Avec cet opérateur de composition:
(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c
(m >=> n) x = do
y <- m x
n y
return >=> g = g
f >=> return = f
(f >=> g) >=> h = f >=> (g >=> h)
liftM :: (Monad m) => (a -> b) -> m a -> m b
liftM f m = m >>= (\x -> return (f x))
liftM :: (Monad m) => (a -> b) -> m a -> m b
liftM f m = do
x <- m
return (f x)
On peut citer join, mapM, filterM, foldM, entre autres.
join :: Monad m => m (m a) -> m a
mapM :: Monad m => (a -> m b) -> [a] -> m [b]
mapM_ :: Monad m => (a -> m b) -> [a] -> m ()
filterM :: Monad m => (a -> m Bool) -> [a] -> m [a]
foldM :: Monad m => (a -> b -> m a) -> a -> [b] -> m a
foldM_ :: Monad m => (a -> b -> m a) -> a -> [b] -> m ()
Voir cette page.
IO est une monade, permet de séparer le code pur du code impur.do.