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 ».map
er 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) xs
zipWith ($) fs xs
(<$>) :: (Functor f) => (a -> b) -> f a -> f b
f <$> x = fmap f x -- pas le même f que ci-dessus
instance 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.
do
main :: 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)
do
main = demander >>= (\input1 ->
demander >>= (\input2 ->
affiche (input2 ++ input1)))
main = demander >>= \input1 ->
demander >>= \input2 ->
affiche (input2 ++ input1)
main = do
input1 <- demander
input2 <- demander
affiche (input2 ++ input1)
do
do
assemble les actions.<-
do
ne peut être bindée,do
.return
return
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
.