Haskell Language
Monades
Recherche…
Introduction
Une monade est un type de données d'actions composables. Monad
est la classe des constructeurs de types dont les valeurs représentent de telles actions. Peut-être IO
est le plus reconnaissable d' un: une valeur de IO a
une « recette pour récupérer une a
valeur du monde réel ».
Nous disons qu'un constructeur de type m
(tel que []
ou Maybe
) forme une monade s'il existe une instance Monad m
satisfaisant certaines lois sur la composition des actions. On peut alors raisonner sur ma
comme une "action dont le résultat a tapé a
".
La monade peut-être
Maybe
- Maybe
est-il utilisé pour représenter des valeurs éventuellement vides - similaires à null
dans les autres langues. Habituellement, il est utilisé comme type de sortie des fonctions qui peuvent échouer d'une certaine manière.
Considérons la fonction suivante:
halve :: Int -> Maybe Int
halve x
| even x = Just (x `div` 2)
| odd x = Nothing
Pensez à halve
de halve
en tant qu'action, en fonction d'un Int
, qui tente de réduire de moitié l'entier, en cas d'échec s'il est impair.
Comment halve
nous par un entier trois fois?
takeOneEighth :: Int -> Maybe Int -- (after you read the 'do' sub-section:)
takeOneEighth x =
case halve x of -- do {
Nothing -> Nothing
Just oneHalf -> -- oneHalf <- halve x
case halve oneHalf of
Nothing -> Nothing
Just oneQuarter -> -- oneQuarter <- halve oneHalf
case halve oneQuarter of
Nothing -> Nothing -- oneEighth <- halve oneQuarter
Just oneEighth ->
Just oneEighth -- return oneEighth }
-
takeOneEighth
est une séquence de trois étapes dehalve
takeOneEighth
halve
. - Si une
halve
étape échoue, nous voulons que toute la compositiontakeOneEighth
échoue. - Si une
halve
étape réussit, nous voulons que son résultat soit reporté.
instance Monad Maybe where
-- (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
Nothing >>= f = Nothing -- infixl 1 >>=
Just x >>= f = Just (f x) -- also, f =<< m = m >>= f
-- return :: a -> Maybe a
return x = Just x
et maintenant nous pouvons écrire:
takeOneEighth :: Int -> Maybe Int
takeOneEighth x = halve x >>= halve >>= halve -- or,
-- return x >>= halve >>= halve >>= halve -- which is parsed as
-- (((return x) >>= halve) >>= halve) >>= halve -- which can also be written as
-- (halve =<<) . (halve =<<) . (halve =<<) $ return x -- or, equivalently, as
-- halve <=< halve <=< halve $ x
La composition de Kleisli <=<
est définie comme (g <=< f) x = g =<< fx
ou, de manière équivalente, comme (f >=> g) x = fx >>= g
. Avec elle la définition ci-dessus devient juste
takeOneEighth :: Int -> Maybe Int
takeOneEighth = halve <=< halve <=< halve -- infixr 1 <=<
-- or, equivalently,
-- halve >=> halve >=> halve -- infixr 1 >=>
Chaque monade doit obéir à trois lois de la monade, c'est-à-dire à tout type qui est une instance de la classe Monad
:
1. return x >>= f = f x
2. m >>= return = m
3. (m >>= g) >>= h = m >>= (\y -> g y >>= h)
où m
est une monade, f
a le type a -> mb
et g
a le type b -> mc
.
Ou de manière équivalente, en utilisant l'opérateur de composition >=>
Kleisli défini ci-dessus:
1. return >=> g = g -- do { y <- return x ; g y } == g x
2. f >=> return = f -- do { y <- f x ; return y } == f x
3. (f >=> g) >=> h = f >=> (g >=> h) -- do { z <- do { y <- f x; g y } ; h z }
-- == do { y <- f x ; do { z <- g y; h z } }
L'obéissance à ces lois rend la raison de la monade beaucoup plus facile à comprendre, car elle garantit que l'utilisation de fonctions monadiques et leur composition se comportent de manière raisonnable, comme d'autres monades.
Vérifions si la monade Maybe
obéit aux trois lois de la monade.
- La loi d'identité de gauche -
return x >>= f = fx
return z >>= f
= (Just z) >>= f
= f z
- La bonne loi d'identité -
m >>= return = m
-
Just
constructeur de données
Just z >>= return
= return z
= Just z
- Constructeur de données
Nothing
Nothing >>= return
= Nothing
- La loi d'associativité -
(m >>= f) >>= g = m >>= (\x -> fx >>= g)
-
Just
constructeur de données
-- Left-hand side
((Just z) >>= f) >>= g
= f z >>= g
-- Right-hand side
(Just z) >>= (\x -> f x >>= g)
(\x -> f x >>= g) z
= f z >>= g
- Constructeur de données
Nothing
-- Left-hand side
(Nothing >>= f) >>= g
= Nothing >>= g
= Nothing
-- Right-hand side
Nothing >>= (\x -> f x >>= g)
= Nothing
IO monade
Il n'y a aucun moyen d'obtenir une valeur de type a
sur une expression de type IO a
et il ne devrait pas y en avoir. C'est en fait une grande partie de la raison pour laquelle les monades sont utilisées pour modéliser les IO
.
Une expression de type IO a
peut être considérée comme représentant une action pouvant interagir avec le monde réel et, si elle est exécutée, entraînerait quelque chose de type a
. Par exemple, la fonction getLine :: IO String
du prélude ne signifie pas que sous getLine
il existe une chaîne spécifique que je peux extraire - cela signifie que getLine
représente l'action getLine
obtenir une ligne à partir d'une entrée standard.
Sans surprise, main :: IO ()
depuis un programme Haskell représente un calcul / une action qui interagit avec le monde réel.
Les choses que vous pouvez faire pour les expressions de type IO a
car IO
est une monade:
Séquence de deux actions en utilisant
(>>)
pour produire une nouvelle action qui exécute la première action, rejette la valeur produite, puis exécute la seconde action.-- print the lines "Hello" then "World" to stdout putStrLn "Hello" >> putStrLn "World"
Parfois, vous ne voulez pas ignorer la valeur qui a été produite lors de la première action. En fait, vous souhaitez qu’elle soit ajoutée à une seconde action. Pour cela, nous avons
>>=
. PourIO
, il a le type(>>=) :: IO a -> (a -> IO b) -> IO b
.-- get a line from stdin and print it back out getLine >>= putStrLn
Prenez une valeur normale et convertissez-la en une action qui retourne immédiatement la valeur que vous lui avez donnée. Cette fonction est moins utile jusqu'à ce que vous commenciez à utiliser la notation
do
.-- make an action that just returns 5 return 5
Plus d'informations sur le wiki Haskell sur la monade IO ici .
Liste Monad
Les listes forment une monade. Ils ont une instanciation de monade équivalente à celle-ci:
instance Monad [] where return x = [x] xs >>= f = concat (map f xs)
Nous pouvons les utiliser pour émuler le non-déterminisme dans nos calculs. Lorsque nous utilisons xs >>= f
, la fonction f :: a -> [b]
est mappée sur la liste xs
, en obtenant une liste de résultats de chaque application de f
sur chaque élément de xs
, et toutes les listes de les résultats sont ensuite concaténés dans une liste de tous les résultats. A titre d'exemple, nous calculons une somme de deux nombres non déterministes en utilisant la notation do , la somme étant représentée par la liste des sommes de toutes les paires d'entiers de deux listes, chaque liste représentant toutes les valeurs possibles d'un nombre non déterministe:
sumnd xs ys = do
x <- xs
y <- ys
return (x + y)
Ou, de manière équivalente, en utilisant liftM2
dans Control.Monad
:
sumnd = liftM2 (+)
on obtient:
> sumnd [1,2,3] [0,10]
[1,11,2,12,3,13]
Monad comme sous-classe d'application
A partir de GHC 7.10, Applicative
est une super-classe de Monad
(c’est-à-dire que chaque type qui est une Monad
doit également être un Applicative
). Toutes les méthodes de Applicative
( pure
, <*>
) peuvent être implémentées en termes de méthodes de Monad
( return
, >>=
).
Il est évident que le pure
et le return
servent des objectifs équivalents, donc pure = return
. La définition de <*>
est trop claire:
mf <*> mx = do { f <- mf; x <- mx; return (f x) }
-- = mf >>= (\f -> mx >>= (\x -> return (f x)))
-- = [r | f <- mf, x <- mx, r <- return (f x)] -- with MonadComprehensions
-- = [f x | f <- mf, x <- mx]
Cette fonction est définie comme ap
dans les bibliothèques standard.
Ainsi, si vous avez déjà défini une instance de Monad
pour un type, vous pouvez effectivement obtenir une instance de Applicative
"gratuitement" en définissant
instance Applicative < type > where
pure = return
(<*>) = ap
Comme pour les lois monades, ces équivalences ne sont pas appliquées, mais les développeurs doivent s’assurer qu’elles sont toujours respectées.
Pas de moyen général d'extraire de la valeur d'un calcul monadique
Vous pouvez envelopper des valeurs dans des actions et transférer le résultat d'un calcul dans un autre:
return :: Monad m => a -> m a
(>>=) :: Monad m => m a -> (a -> m b) -> m b
Cependant, la définition d'une Monade ne garantit pas l'existence d'une fonction de type Monad m => ma -> a
.
Cela signifie qu’il n’existe en général aucun moyen d’extraire une valeur d’un calcul (par exemple, le «déplier»). C'est le cas pour de nombreuses instances:
extract :: Maybe a -> a
extract (Just x) = x -- Sure, this works, but...
extract Nothing = undefined -- We can’t extract a value from failure.
Plus précisément, il n'y a pas de fonction IO a -> a
, qui confond souvent les débutants; voir cet exemple .
do-notation
do
-notation est le sucre syntaxique pour les monades. Voici les règles:
do x <- mx do x <- mx y <- my is equivalent to do y <- my ... ...
do let a = b let a = b in ... is equivalent to do ...
do m m >> ( e is equivalent to e)
do x <- m m >>= (\x -> e is equivalent to e)
do m is equivalent to m
Par exemple, ces définitions sont équivalentes:
example :: IO Integer
example =
putStrLn "What's your name?" >> (
getLine >>= (\name ->
putStrLn ("Hello, " ++ name ++ ".") >> (
putStrLn "What should we return?" >> (
getLine >>= (\line ->
let n = (read line :: Integer) in
return (n + n))))))
example :: IO Integer
example = do
putStrLn "What's your name?"
name <- getLine
putStrLn ("Hello, " ++ name ++ ".")
putStrLn "What should we return?"
line <- getLine
let n = (read line :: Integer)
return (n + n)
Définition de Monade
class Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
La fonction la plus importante pour traiter les monades est l' opérateur de liaison >>=
:
(>>=) :: m a -> (a -> m b) -> m b
- Pensez à
ma
comme "une action aveca
résultat" . - Considérez
a -> mb
comme «une action (dépendant d’a
paramètre) avec un résultatb
.» .
>>=
séquence deux actions en canalisant le résultat de la première action à la seconde.
L'autre fonction définie par Monad
est:
return :: a -> m a
Son nom est regrettable: ce return
n'a rien à voir avec le mot-clé de return
trouvé dans les langages de programmation impératifs.
return x
est l'action triviale donnant x
comme résultat. (C'est trivial dans le sens suivant :)
return x >>= f ≡ f x -- “left identity” monad law
x >>= return ≡ x -- “right identity” monad law