tl; dr
{-# LANGUAGE InstanceSigs #-}
newtype Id t = Id t
instance Monad Id where
return :: t -> Id t
return = Id
(=<<) :: (a -> Id b) -> Id a -> Id b
f =<< (Id x) = f x
Prologue
L'opérateur d'application $
des fonctions
forall a b. a -> b
est défini canoniquement
($) :: (a -> b) -> a -> b
f $ x = f x
infixr 0 $
en termes d'application de la fonction primitive de Haskell f x
( infixl 10
).
Composition .
est définie en termes de $
comme
(.) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = \ x -> f $ g x
infixr 9 .
et satisfait les équivalences forall f g h.
f . id = f :: c -> d Right identity
id . g = g :: b -> c Left identity
(f . g) . h = f . (g . h) :: a -> d Associativity
.
est associative, et id
est son identité droite et gauche.
Le triple Kleisli
En programmation, une monade est un constructeur de type foncteur avec une instance de la classe de type monade. Il existe plusieurs variantes équivalentes de définition et de mise en œuvre, chacune portant des intuitions légèrement différentes sur l'abstraction de la monade.
Un functor est un constructeur f
de type de type * -> *
avec une instance de la classe de type functor.
{-# LANGUAGE KindSignatures #-}
class Functor (f :: * -> *) where
map :: (a -> b) -> (f a -> f b)
En plus de suivre le protocole de type appliqué statiquement, les instances de la classe de type foncteur doivent obéir aux lois algébriques des foncteurs forall f g.
map id = id :: f t -> f t Identity
map f . map g = map (f . g) :: f a -> f c Composition / short cut fusion
Les calculs de foncteurs ont le type
forall f t. Functor f => f t
Un calcul c r
consiste en des résultats r
dans le contexte c
.
Les fonctions monaires unaires ou les flèches de Kleisli ont le type
forall m a b. Functor m => a -> m b
Les flèches Kleisi sont des fonctions qui prennent un argument a
et retournent un calcul monadique m b
.
Les monades sont définies canoniquement en fonction du triple de Kleisli forall m. Functor m =>
(m, return, (=<<))
implémenté comme classe de type
class Functor m => Monad m where
return :: t -> m t
(=<<) :: (a -> m b) -> m a -> m b
infixr 1 =<<
L' identité Kleisli return
est une flèche Kleisli qui promeut une valeur t
dans un contexte monadique m
. L'extension ou l' application Kleisli =<<
applique une flèche Kleisli a -> m b
aux résultats d'un calcul m a
.
La composition de Kleisli <=<
est définie en termes d'extension comme
(<=<) :: Monad m => (b -> m c) -> (a -> m b) -> (a -> m c)
f <=< g = \ x -> f =<< g x
infixr 1 <=<
<=<
compose deux flèches Kleisli, en appliquant la flèche gauche aux résultats de l'application de la flèche droite.
Les instances de la classe de type monade doivent obéir aux lois de la monade , le plus élégamment énoncées en termes de composition de Kleisli:forall f g h.
f <=< return = f :: c -> m d Right identity
return <=< g = g :: b -> m c Left identity
(f <=< g) <=< h = f <=< (g <=< h) :: a -> m d Associativity
<=<
est associative, et return
est son identité droite et gauche.
Identité
Le type d'identité
type Id t = t
est la fonction d'identité sur les types
Id :: * -> *
Interprété comme foncteur,
return :: t -> Id t
= id :: t -> t
(=<<) :: (a -> Id b) -> Id a -> Id b
= ($) :: (a -> b) -> a -> b
(<=<) :: (b -> Id c) -> (a -> Id b) -> (a -> Id c)
= (.) :: (b -> c) -> (a -> b) -> (a -> c)
Dans Haskell canonique, la monade d'identité est définie
newtype Id t = Id t
instance Functor Id where
map :: (a -> b) -> Id a -> Id b
map f (Id x) = Id (f x)
instance Monad Id where
return :: t -> Id t
return = Id
(=<<) :: (a -> Id b) -> Id a -> Id b
f =<< (Id x) = f x
Option
Un type d'option
data Maybe t = Nothing | Just t
code un calcul Maybe t
qui ne donne pas nécessairement un résultat t
, un calcul qui peut «échouer». L'option monade est définie
instance Functor Maybe where
map :: (a -> b) -> (Maybe a -> Maybe b)
map f (Just x) = Just (f x)
map _ Nothing = Nothing
instance Monad Maybe where
return :: t -> Maybe t
return = Just
(=<<) :: (a -> Maybe b) -> Maybe a -> Maybe b
f =<< (Just x) = f x
_ =<< Nothing = Nothing
a -> Maybe b
n'est appliqué à un résultat que s'il Maybe a
donne un résultat.
newtype Nat = Nat Int
Les nombres naturels peuvent être codés comme ces entiers supérieurs ou égaux à zéro.
toNat :: Int -> Maybe Nat
toNat i | i >= 0 = Just (Nat i)
| otherwise = Nothing
Les nombres naturels ne sont pas fermés par soustraction.
(-?) :: Nat -> Nat -> Maybe Nat
(Nat n) -? (Nat m) = toNat (n - m)
infixl 6 -?
L'option monad couvre une forme de base de gestion des exceptions.
(-? 20) <=< toNat :: Int -> Maybe Nat
liste
La liste monad, sur le type de liste
data [] t = [] | t : [t]
infixr 5 :
et son fonctionnement monoïde additif "append"
(++) :: [t] -> [t] -> [t]
(x : xs) ++ ys = x : xs ++ ys
[] ++ ys = ys
infixr 5 ++
code un calcul non linéaire[t]
produisant une quantité naturelle 0, 1, ...
de résultats t
.
instance Functor [] where
map :: (a -> b) -> ([a] -> [b])
map f (x : xs) = f x : map f xs
map _ [] = []
instance Monad [] where
return :: t -> [t]
return = (: [])
(=<<) :: (a -> [b]) -> [a] -> [b]
f =<< (x : xs) = f x ++ (f =<< xs)
_ =<< [] = []
L'extension =<<
concatène ++
toutes les listes [b]
résultant des applications f x
d'une flèche de Kleisli a -> [b]
aux éléments de [a]
dans une seule liste de résultats [b]
.
Que les diviseurs propres d'un entier positif n
soient
divisors :: Integral t => t -> [t]
divisors n = filter (`divides` n) [2 .. n - 1]
divides :: Integral t => t -> t -> Bool
(`divides` n) = (== 0) . (n `rem`)
puis
forall n. let { f = f <=< divisors } in f n = []
Pour définir la classe de type monade, au lieu de l'extension =<<
, la norme Haskell utilise son flip, l' opérateur de liaison>>=
.
class Applicative m => Monad m where
(>>=) :: forall a b. m a -> (a -> m b) -> m b
(>>) :: forall a b. m a -> m b -> m b
m >> k = m >>= \ _ -> k
{-# INLINE (>>) #-}
return :: a -> m a
return = pure
Par souci de simplicité, cette explication utilise la hiérarchie des classes de types
class Functor f
class Functor m => Monad m
Dans Haskell, la hiérarchie standard actuelle est
class Functor f
class Functor p => Applicative p
class Applicative m => Monad m
car non seulement chaque monade est un foncteur, mais chaque applicatif est un foncteur et chaque monade est également un applicatif.
En utilisant la liste monad, le pseudocode impératif
for a in (1, ..., 10)
for b in (1, ..., 10)
p <- a * b
if even(p)
yield p
se traduit approximativement par le bloc do ,
do a <- [1 .. 10]
b <- [1 .. 10]
let p = a * b
guard (even p)
return p
la compréhension équivalente de la monade ,
[ p | a <- [1 .. 10], b <- [1 .. 10], let p = a * b, even p ]
et l'expression
[1 .. 10] >>= (\ a ->
[1 .. 10] >>= (\ b ->
let p = a * b in
guard (even p) >> -- [ () | even p ] >>
return p
)
)
La notation Do et les compréhensions monade sont des éléments syntaxiques pour les expressions de liaison imbriquées. L'opérateur de liaison est utilisé pour la liaison de nom local des résultats monadiques.
let x = v in e = (\ x -> e) $ v = v & (\ x -> e)
do { r <- m; c } = (\ r -> c) =<< m = m >>= (\ r -> c)
où
(&) :: a -> (a -> b) -> b
(&) = flip ($)
infixl 0 &
La fonction de garde est définie
guard :: Additive m => Bool -> m ()
guard True = return ()
guard False = fail
où le type d'unité ou «tuple vide»
data () = ()
Les monades additives qui prennent en charge le choix et l' échec peuvent être abstraites à l'aide d'une classe de type
class Monad m => Additive m where
fail :: m t
(<|>) :: m t -> m t -> m t
infixl 3 <|>
instance Additive Maybe where
fail = Nothing
Nothing <|> m = m
m <|> _ = m
instance Additive [] where
fail = []
(<|>) = (++)
où fail
et <|>
former un monoïdeforall k l m.
k <|> fail = k
fail <|> l = l
(k <|> l) <|> m = k <|> (l <|> m)
et fail
est l'élément zéro absorbant / annihilant des monades additives
_ =<< fail = fail
Si dans
guard (even p) >> return p
even p
est vrai, alors la garde produit [()]
, et, par la définition de >>
, la fonction constante locale
\ _ -> return p
est appliqué au résultat ()
. Si faux, le garde produit la liste monad fail
( []
), qui ne donne aucun résultat pour qu'une flèche de Kleisli soit appliquée >>
, donc cela p
est ignoré.
Etat
Malheureusement, les monades sont utilisées pour coder le calcul avec état.
Un processeur d'état est une fonction
forall st t. st -> (t, st)
qui transite un état st
et donne un résultat t
. L' État st
peut être n'importe quoi. Rien, drapeau, compte, tableau, poignée, machine, monde.
Le type de processeur d'État est généralement appelé
type State st t = st -> (t, st)
La monade du processeur d'état est le * -> *
foncteur du genre State st
. Les flèches de Kleisli de la monade du processeur d'état sont des fonctions
forall st a b. a -> (State st) b
Dans Haskell canonique, la version paresseuse de la monade de processeur d'état est définie
newtype State st t = State { stateProc :: st -> (t, st) }
instance Functor (State st) where
map :: (a -> b) -> ((State st) a -> (State st) b)
map f (State p) = State $ \ s0 -> let (x, s1) = p s0
in (f x, s1)
instance Monad (State st) where
return :: t -> (State st) t
return x = State $ \ s -> (x, s)
(=<<) :: (a -> (State st) b) -> (State st) a -> (State st) b
f =<< (State p) = State $ \ s0 -> let (x, s1) = p s0
in stateProc (f x) s1
Un processeur d'état est exécuté en fournissant un état initial:
run :: State st t -> st -> (t, st)
run = stateProc
eval :: State st t -> st -> t
eval = fst . run
exec :: State st t -> st -> st
exec = snd . run
L'accès à l'État est fourni par des primitives get
et des put
méthodes d'abstraction sur des monades avec état :
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}
class Monad m => Stateful m st | m -> st where
get :: m st
put :: st -> m ()
m -> st
déclare une dépendance fonctionnelle du type d'état st
sur la monade m
; qu'un State t
, par exemple, déterminera le type d'état de manière t
unique.
instance Stateful (State st) st where
get :: State st st
get = State $ \ s -> (s, s)
put :: st -> State st ()
put s = State $ \ _ -> ((), s)
avec le type d'unité utilisé de manière analogue à void
en C.
modify :: Stateful m st => (st -> st) -> m ()
modify f = do
s <- get
put (f s)
gets :: Stateful m st => (st -> t) -> m t
gets f = do
s <- get
return (f s)
gets
est souvent utilisé avec les accesseurs de champ d'enregistrement.
L'équivalent monade d'état du thread variable
let s0 = 34
s1 = (+ 1) s0
n = (* 12) s1
s2 = (+ 7) s1
in (show n, s2)
où s0 :: Int
, est le tout aussi référentiellement transparent, mais infiniment plus élégant et pratique
(flip run) 34
(do
modify (+ 1)
n <- gets (* 12)
modify (+ 7)
return (show n)
)
modify (+ 1)
est un calcul de type State Int ()
, sauf pour son effet équivalent à return ()
.
(flip run) 34
(modify (+ 1) >>
gets (* 12) >>= (\ n ->
modify (+ 7) >>
return (show n)
)
)
La loi monade de l'associativité peut être écrite en termes de >>=
forall m f g.
(m >>= f) >>= g = m >>= (\ x -> f x >>= g)
ou
do { do { do {
r1 <- do { x <- m; r0 <- m;
r0 <- m; = do { = r1 <- f r0;
f r0 r1 <- f x; g r1
}; g r1 }
g r1 }
} }
Comme dans la programmation orientée expression (par exemple Rust), la dernière instruction d'un bloc représente son rendement. L'opérateur de liaison est parfois appelé «point-virgule programmable».
Les primitives de structure de contrôle d'itération de la programmation impérative structurée sont émulées de façon monadique
for :: Monad m => (a -> m b) -> [a] -> m ()
for f = foldr ((>>) . f) (return ())
while :: Monad m => m Bool -> m t -> m ()
while c m = do
b <- c
if b then m >> while c m
else return ()
forever :: Monad m => m t
forever m = m >> forever m
Entrée sortie
data World
La monade de processeur d'état mondial d'E / S est une réconciliation de Haskell pur et du monde réel, de sémantique opérationnelle dénotative et impérative fonctionnelle. Un analogue proche de la mise en œuvre stricte réelle:
type IO t = World -> (t, World)
L'interaction est facilitée par des primitives impures
getChar :: IO Char
putChar :: Char -> IO ()
readFile :: FilePath -> IO String
writeFile :: FilePath -> String -> IO ()
hSetBuffering :: Handle -> BufferMode -> IO ()
hTell :: Handle -> IO Integer
. . . . . .
L'impureté du code qui utilise des IO
primitives est protocoleée en permanence par le système de type. Parce que la pureté est géniale, ce qui se passe en IO
reste IO
.
unsafePerformIO :: IO t -> t
Ou, du moins, devrait.
La signature de type d'un programme Haskell
main :: IO ()
main = putStrLn "Hello, World!"
s'étend à
World -> ((), World)
Une fonction qui transforme un monde.
Épilogue
La catégorie des objets qui sont des types Haskell et des morphismes qui sont des fonctions entre les types Haskell est, "rapide et lâche", la catégorie Hask
.
Un foncteur T
est un mappage d'une catégorie C
à une catégorie D
; pour chaque objet dans C
un objetD
Tobj : Obj(C) -> Obj(D)
f :: * -> *
et pour chaque morphisme dans C
un morphismeD
Tmor : HomC(X, Y) -> HomD(Tobj(X), Tobj(Y))
map :: (a -> b) -> (f a -> f b)
où X
, Y
sont les objets C
. HomC(X, Y)
est la classe d'homomorphisme de tous les morphismes X -> Y
en C
. Le foncteur doit préserver l'identité et la composition du morphisme, la «structure» de C
, en D
.
Tmor Tobj
T(id) = id : T(X) -> T(X) Identity
T(f) . T(g) = T(f . g) : T(X) -> T(Z) Composition
La catégorie Kleisli d'une catégorie C
est donnée par un triple Kleisli
<T, eta, _*>
d'un endofuncteur
T : C -> C
( f
), un morphisme d'identité eta
( return
) et un opérateur d'extension *
( =<<
).
Chaque morphisme de Kleisli dans Hask
f : X -> T(Y)
f :: a -> m b
par l'opérateur d'extension
(_)* : Hom(X, T(Y)) -> Hom(T(X), T(Y))
(=<<) :: (a -> m b) -> (m a -> m b)
reçoit un morphisme dans Hask
la catégorie de Kleisli
f* : T(X) -> T(Y)
(f =<<) :: m a -> m b
La composition dans la catégorie Kleisli .T
est donnée en termes d'extension
f .T g = f* . g : X -> T(Z)
f <=< g = (f =<<) . g :: a -> m c
et satisfait les axiomes de catégorie
eta .T g = g : Y -> T(Z) Left identity
return <=< g = g :: b -> m c
f .T eta = f : Z -> T(U) Right identity
f <=< return = f :: c -> m d
(f .T g) .T h = f .T (g .T h) : X -> T(U) Associativity
(f <=< g) <=< h = f <=< (g <=< h) :: a -> m d
qui, en appliquant les transformations d'équivalence
eta .T g = g
eta* . g = g By definition of .T
eta* . g = id . g forall f. id . f = f
eta* = id forall f g h. f . h = g . h ==> f = g
(f .T g) .T h = f .T (g .T h)
(f* . g)* . h = f* . (g* . h) By definition of .T
(f* . g)* . h = f* . g* . h . is associative
(f* . g)* = f* . g* forall f g h. f . h = g . h ==> f = g
en termes d'extension sont donnés canoniquement
eta* = id : T(X) -> T(X) Left identity
(return =<<) = id :: m t -> m t
f* . eta = f : Z -> T(U) Right identity
(f =<<) . return = f :: c -> m d
(f* . g)* = f* . g* : T(X) -> T(Z) Associativity
(((f =<<) . g) =<<) = (f =<<) . (g =<<) :: m a -> m c
Les monades peuvent également être définies en termes non pas d'extension kleislienne, mais d'une transformation naturelle mu
, appelée programmation join
. Une monade est définie en termes de mu
triple sur une catégorie C
, d'un endofoncteur
T : C -> C
f :: * -> *
et deux transformations naturelles
eta : Id -> T
return :: t -> f t
mu : T . T -> T
join :: f (f t) -> f t
satisfaire les équivalences
mu . T(mu) = mu . mu : T . T . T -> T . T Associativity
join . map join = join . join :: f (f (f t)) -> f t
mu . T(eta) = mu . eta = id : T -> T Identity
join . map return = join . return = id :: f t -> f t
La classe de type monade est alors définie
class Functor m => Monad m where
return :: t -> m t
join :: m (m t) -> m t
L' mu
implémentation canonique de l'option monade:
instance Monad Maybe where
return = Just
join (Just m) = m
join Nothing = Nothing
La concat
fonction
concat :: [[a]] -> [a]
concat (x : xs) = x ++ concat xs
concat [] = []
est la join
monade de la liste.
instance Monad [] where
return :: t -> [t]
return = (: [])
(=<<) :: (a -> [b]) -> ([a] -> [b])
(f =<<) = concat . map f
Les implémentations de join
peuvent être traduites à partir du formulaire d'extension en utilisant l'équivalence
mu = id* : T . T -> T
join = (id =<<) :: m (m t) -> m t
La traduction inverse de mu
vers extension est donnée par
f* = mu . T(f) : T(X) -> T(Y)
(f =<<) = join . map f :: m a -> m b
Mais pourquoi une théorie aussi abstraite devrait-elle être utile à la programmation?
La réponse est simple: en tant qu'informaticiens, nous apprécions l'abstraction ! Lorsque nous concevons l'interface avec un composant logiciel, nous voulons qu'elle révèle le moins possible sur l'implémentation. Nous voulons pouvoir remplacer l'implémentation par de nombreuses alternatives, de nombreuses autres «instances» du même «concept». Lorsque nous concevons une interface générique pour de nombreuses bibliothèques de programmes, il est encore plus important que l'interface que nous choisissons ait une variété d'implémentations. C'est la généralité du concept de monade que nous apprécions tellement, c'est parce que la théorie des catégories est si abstraite que ses concepts sont si utiles pour la programmation.
Il n'est donc guère surprenant que la généralisation des monades que nous présentons ci-dessous soit également étroitement liée à la théorie des catégories. Mais nous soulignons que notre objectif est très pratique: il ne s'agit pas de «mettre en œuvre la théorie des catégories», c'est de trouver un moyen plus général de structurer les bibliothèques combinatoires. C'est simplement notre chance que les mathématiciens aient déjà fait une grande partie du travail pour nous!
de la généralisation des monades aux flèches par John Hughes