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
Prolog
Der Anwendungsoperator $
von Funktionen
forall a b. a -> b
ist kanonisch definiert
($) :: (a -> b) -> a -> b
f $ x = f x
infixr 0 $
in Bezug auf die Anwendung der Haskell-primitiven Funktion f x
( infixl 10
).
Die Zusammensetzung .
wird definiert $
als
(.) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = \ x -> f $ g x
infixr 9 .
und erfüllt die Äquivalenzen 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
.
ist assoziativ und id
ist seine rechte und linke Identität.
Das Kleisli Triple
Bei der Programmierung ist eine Monade ein Konstruktor vom Typ Funktor mit einer Instanz der Klasse vom Typ Monade. Es gibt mehrere äquivalente Varianten der Definition und Implementierung, die jeweils leicht unterschiedliche Intuitionen über die Monadenabstraktion enthalten.
Ein Funktor ist ein f
Typkonstruktor * -> *
mit einer Instanz der Funktortypklasse.
{-# LANGUAGE KindSignatures #-}
class Functor (f :: * -> *) where
map :: (a -> b) -> (f a -> f b)
Zusätzlich zum Befolgen des statisch erzwungenen Typprotokolls müssen Instanzen der Funktortypklasse den algebraischen Funktorgesetzen entsprechen 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
Functor Berechnungen haben den Typ
forall f t. Functor f => f t
Eine Berechnung c r
besteht aus Ergebnissen r
im Kontext c
.
Unäre monadische Funktionen oder Kleisli-Pfeile haben den Typ
forall m a b. Functor m => a -> m b
Kleisi-Pfeile sind Funktionen, die ein Argument annehmen a
und eine monadische Berechnung zurückgeben m b
.
Monaden werden kanonisch im Sinne des Kleisli-Tripels definiert forall m. Functor m =>
(m, return, (=<<))
implementiert als Typklasse
class Functor m => Monad m where
return :: t -> m t
(=<<) :: (a -> m b) -> m a -> m b
infixr 1 =<<
Die Kleisli-Identität return
ist ein Kleisli-Pfeil, der einen Wert t
in einen monadischen Kontext fördert m
. Die Erweiterung oder Kleisli-Anwendung =<<
wendet einen Kleisli-Pfeil a -> m b
auf die Ergebnisse einer Berechnung an m a
.
Die Kleisli-Komposition <=<
wird in Bezug auf die Erweiterung definiert als
(<=<) :: Monad m => (b -> m c) -> (a -> m b) -> (a -> m c)
f <=< g = \ x -> f =<< g x
infixr 1 <=<
<=<
setzt zwei Kleisli-Pfeile zusammen und wendet den linken Pfeil auf die Ergebnisse der Anwendung des rechten Pfeils an.
Instanzen der Monadentypklasse müssen den Monadengesetzen entsprechen , die in Bezug auf die Kleisli-Zusammensetzung am elegantesten angegeben sind: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
<=<
ist assoziativ und return
ist seine rechte und linke Identität.
Identität
Der Identitätstyp
type Id t = t
ist die Identitätsfunktion für Typen
Id :: * -> *
Als Funktor interpretiert,
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)
Im kanonischen Haskell wird die Identitätsmonade definiert
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
Möglichkeit
Ein Optionstyp
data Maybe t = Nothing | Just t
codiert Berechnungen Maybe t
, die nicht unbedingt zu einem Ergebnis führen t
, Berechnungen, die möglicherweise „fehlschlagen“. Die Option Monade ist definiert
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
wird nur dann auf ein Ergebnis angewendet, wenn Maybe a
ein Ergebnis erhalten wird.
newtype Nat = Nat Int
Die natürlichen Zahlen können als ganze Zahlen größer oder gleich Null codiert werden.
toNat :: Int -> Maybe Nat
toNat i | i >= 0 = Just (Nat i)
| otherwise = Nothing
Die natürlichen Zahlen werden bei Subtraktion nicht geschlossen.
(-?) :: Nat -> Nat -> Maybe Nat
(Nat n) -? (Nat m) = toNat (n - m)
infixl 6 -?
Die Option Monade deckt eine Grundform der Ausnahmebehandlung ab.
(-? 20) <=< toNat :: Int -> Maybe Nat
Liste
Die Listenmonade über dem Listentyp
data [] t = [] | t : [t]
infixr 5 :
und seine additive Monoidoperation "anhängen"
(++) :: [t] -> [t] -> [t]
(x : xs) ++ ys = x : xs ++ ys
[] ++ ys = ys
infixr 5 ++
codiert nichtlineare Berechnungen, [t]
die eine natürliche Menge 0, 1, ...
an Ergebnissen liefern 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)
_ =<< [] = []
Die Erweiterung =<<
verkettet ++
alle Listen, [b]
die sich aus der Anwendung f x
eines Kleisli-Pfeils ergeben, a -> [b]
zu Elementen [a]
einer einzigen Ergebnisliste [b]
.
Die richtigen Teiler einer positiven ganzen Zahl n
seien
divisors :: Integral t => t -> [t]
divisors n = filter (`divides` n) [2 .. n - 1]
divides :: Integral t => t -> t -> Bool
(`divides` n) = (== 0) . (n `rem`)
dann
forall n. let { f = f <=< divisors } in f n = []
Bei der Definition der Monadentypklasse verwendet =<<
der Haskell-Standard anstelle der Erweiterung seinen Flip, den Bind- Operator >>=
.
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
In dieser Erklärung wird der Einfachheit halber die Typklassenhierarchie verwendet
class Functor f
class Functor m => Monad m
In Haskell ist die aktuelle Standardhierarchie
class Functor f
class Functor p => Applicative p
class Applicative m => Monad m
denn nicht nur jede Monade ist ein Funktor, sondern jede Anwendung ist eine Funktion und jede Monade ist auch eine Anwendung.
Unter Verwendung der Listenmonade wird der imperative Pseudocode verwendet
for a in (1, ..., 10)
for b in (1, ..., 10)
p <- a * b
if even(p)
yield p
grob übersetzt in den do-Block ,
do a <- [1 .. 10]
b <- [1 .. 10]
let p = a * b
guard (even p)
return p
das äquivalente Monadenverständnis ,
[ p | a <- [1 .. 10], b <- [1 .. 10], let p = a * b, even p ]
und der Ausdruck
[1 .. 10] >>= (\ a ->
[1 .. 10] >>= (\ b ->
let p = a * b in
guard (even p) >> -- [ () | even p ] >>
return p
)
)
Notation und Monadenverständnis sind syntaktischer Zucker für verschachtelte Bindungsausdrücke. Der Bindungsoperator wird für die lokale Namensbindung von monadischen Ergebnissen verwendet.
let x = v in e = (\ x -> e) $ v = v & (\ x -> e)
do { r <- m; c } = (\ r -> c) =<< m = m >>= (\ r -> c)
wo
(&) :: a -> (a -> b) -> b
(&) = flip ($)
infixl 0 &
Die Schutzfunktion ist definiert
guard :: Additive m => Bool -> m ()
guard True = return ()
guard False = fail
wo der Einheitentyp oder "leeres Tupel"
data () = ()
Additive Monaden , die Auswahl und Misserfolg unterstützen, können mithilfe einer Typklasse abstrahiert werden
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 = []
(<|>) = (++)
wo fail
und <|>
bilden ein Monoidforall k l m.
k <|> fail = k
fail <|> l = l
(k <|> l) <|> m = k <|> (l <|> m)
und fail
ist das absorbierende / vernichtende Nullelement von additiven Monaden
_ =<< fail = fail
Wenn in
guard (even p) >> return p
even p
ist wahr, dann erzeugt der Wächter [()]
und nach der Definition >>
der lokalen konstanten Funktion
\ _ -> return p
wird auf das Ergebnis angewendet ()
. Wenn false, erzeugt der Wächter die Liste monad's fail
( []
), die kein Ergebnis für die Anwendung eines Kleisli-Pfeils ergibt >>
, sodass dieser p
übersprungen wird.
Zustand
Monaden werden bekanntlich verwendet, um zustandsbehaftete Berechnungen zu codieren.
Ein Zustandsprozessor ist eine Funktion
forall st t. st -> (t, st)
das übergeht einen Zustand st
und ergibt ein Ergebnis t
. Der Staat st
kann alles sein. Nichts, Flagge, Anzahl, Array, Handle, Maschine, Welt.
Der Typ der Statusprozessoren wird normalerweise aufgerufen
type State st t = st -> (t, st)
Die State Processor Monad ist der freundliche * -> *
Funktor State st
. Kleisli-Pfeile der State-Processor-Monade sind Funktionen
forall st a b. a -> (State st) b
Im kanonischen Haskell ist die Lazy-Version der State Processor-Monade definiert
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
Ein Statusprozessor wird ausgeführt, indem ein Anfangszustand bereitgestellt wird:
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
Der staatliche Zugang wird durch Primitive get
und put
Abstraktionsmethoden über staatsmässige Monaden ermöglicht:
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}
class Monad m => Stateful m st | m -> st where
get :: m st
put :: st -> m ()
m -> st
deklariert eine funktionale Abhängigkeit des Zustandstyps st
von der Monade m
; dass a State t
zum Beispiel den Zustandstyp als t
eindeutig bestimmt.
instance Stateful (State st) st where
get :: State st st
get = State $ \ s -> (s, s)
put :: st -> State st ()
put s = State $ \ _ -> ((), s)
mit dem Einheitentyp analog zu void
in 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
wird häufig mit Datensatzfeld-Accessoren verwendet.
Das Zustandsmonadenäquivalent des variablen Threadings
let s0 = 34
s1 = (+ 1) s0
n = (* 12) s1
s2 = (+ 7) s1
in (show n, s2)
wo s0 :: Int
ist das gleichermaßen referenziell transparent, aber unendlich eleganter und praktischer
(flip run) 34
(do
modify (+ 1)
n <- gets (* 12)
modify (+ 7)
return (show n)
)
modify (+ 1)
ist eine Berechnung des Typs State Int ()
, mit Ausnahme des Effekts, der äquivalent zu ist return ()
.
(flip run) 34
(modify (+ 1) >>
gets (* 12) >>= (\ n ->
modify (+ 7) >>
return (show n)
)
)
Das Monadengesetz der Assoziativität kann in Bezug auf geschrieben werden >>=
forall m f g.
(m >>= f) >>= g = m >>= (\ x -> f x >>= g)
oder
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 }
} }
Wie bei der ausdrucksorientierten Programmierung (z. B. Rust) repräsentiert die letzte Anweisung eines Blocks seine Ausbeute. Der Bindungsoperator wird manchmal als "programmierbares Semikolon" bezeichnet.
Iterationskontrollstrukturprimitive aus der strukturierten imperativen Programmierung werden monadisch emuliert
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
Input-Output
data World
Die Monade des I / O-Weltzustandsprozessors ist eine Versöhnung von reinem Haskell und der realen Welt, von funktionaler denotativer und zwingender operativer Semantik. Ein enges Analogon zur tatsächlichen strengen Umsetzung:
type IO t = World -> (t, World)
Die Interaktion wird durch unreine Grundelemente erleichtert
getChar :: IO Char
putChar :: Char -> IO ()
readFile :: FilePath -> IO String
writeFile :: FilePath -> String -> IO ()
hSetBuffering :: Handle -> BufferMode -> IO ()
hTell :: Handle -> IO Integer
. . . . . .
Die Verunreinigung von Code, der IO
Grundelemente verwendet, wird vom Typsystem permanent protokolliert. Weil Reinheit fantastisch ist IO
, bleibt das , was passiert , drin IO
.
unsafePerformIO :: IO t -> t
Oder sollte es zumindest.
Die Typensignatur eines Haskell-Programms
main :: IO ()
main = putStrLn "Hello, World!"
erweitert sich auf
World -> ((), World)
Eine Funktion, die eine Welt verändert.
Epilog
Die Kategorie, deren Objekte Haskell-Typen sind und deren Morphismen Funktionen zwischen Haskell-Typen sind, ist die Kategorie „schnell und locker“ Hask
.
Ein Funktor T
ist eine Zuordnung von einer Kategorie C
zu einer Kategorie D
. für jedes Objekt in C
einem Objekt inD
Tobj : Obj(C) -> Obj(D)
f :: * -> *
und für jeden Morphismus in C
einem Morphismus inD
Tmor : HomC(X, Y) -> HomD(Tobj(X), Tobj(Y))
map :: (a -> b) -> (f a -> f b)
wo X
, Y
sind Objekte in C
. HomC(X, Y)
ist die Homomorphismusklasse aller Morphismen X -> Y
in C
. Der Funktor muss die Identität und Zusammensetzung des Morphismus bewahren, die „Struktur“ von C
, in D
.
Tmor Tobj
T(id) = id : T(X) -> T(X) Identity
T(f) . T(g) = T(f . g) : T(X) -> T(Z) Composition
Die Kleisli-Kategorie einer Kategorie C
wird durch ein Kleisli-Tripel angegeben
<T, eta, _*>
eines Endofunktors
T : C -> C
( f
), ein Identitätsmorphismus eta
( return
) und ein Erweiterungsoperator *
( =<<
).
Jeder Kleisli-Morphismus in Hask
f : X -> T(Y)
f :: a -> m b
vom Nebenstellenbetreiber
(_)* : Hom(X, T(Y)) -> Hom(T(X), T(Y))
(=<<) :: (a -> m b) -> (m a -> m b)
erhält einen Morphismus in Hask
der Kategorie Kleisli
f* : T(X) -> T(Y)
(f =<<) :: m a -> m b
Die Zusammensetzung in der Kategorie Kleisli .T
wird in Bezug auf die Erweiterung angegeben
f .T g = f* . g : X -> T(Z)
f <=< g = (f =<<) . g :: a -> m c
und erfüllt die Kategorie Axiome
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
welche unter Anwendung der Äquivalenztransformationen
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
in Bezug auf die Erweiterung sind kanonisch gegeben
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
Monaden können auch nicht als Kleislsche Erweiterung, sondern als natürliche Transformation mu
in der Programmierung bezeichnet werden join
. Eine Monade wird mu
als Triple über einer Kategorie C
eines Endofunktors definiert
T : C -> C
f :: * -> *
und zwei natürliche Transformationen
eta : Id -> T
return :: t -> f t
mu : T . T -> T
join :: f (f t) -> f t
Befriedigung der Äquivalenzen
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
Die Monadentypklasse wird dann definiert
class Functor m => Monad m where
return :: t -> m t
join :: m (m t) -> m t
Die kanonische mu
Implementierung der Option Monade:
instance Monad Maybe where
return = Just
join (Just m) = m
join Nothing = Nothing
Die concat
Funktion
concat :: [[a]] -> [a]
concat (x : xs) = x ++ concat xs
concat [] = []
ist die join
der Liste Monade.
instance Monad [] where
return :: t -> [t]
return = (: [])
(=<<) :: (a -> [b]) -> ([a] -> [b])
(f =<<) = concat . map f
Implementierungen von join
können mithilfe der Äquivalenz aus dem Erweiterungsformular übersetzt werden
mu = id* : T . T -> T
join = (id =<<) :: m (m t) -> m t
Die umgekehrte Übersetzung von mu
zum Erweiterungsformular ist gegeben durch
f* = mu . T(f) : T(X) -> T(Y)
(f =<<) = join . map f :: m a -> m b
Aber warum sollte eine so abstrakte Theorie für die Programmierung von Nutzen sein?
Die Antwort ist einfach: Als Informatiker legen wir Wert auf Abstraktion ! Wenn wir die Schnittstelle zu einer Softwarekomponente entwerfen, möchten wir, dass sie so wenig wie möglich über die Implementierung aussagt. Wir möchten in der Lage sein, die Implementierung durch viele Alternativen zu ersetzen, viele andere "Instanzen" desselben "Konzepts". Wenn wir eine generische Schnittstelle für viele Programmbibliotheken entwerfen, ist es noch wichtiger, dass die von uns ausgewählte Schnittstelle eine Vielzahl von Implementierungen aufweist. Es ist die Allgemeinheit des Monadenkonzepts, die wir so hoch schätzen, weil die Kategorietheorie so abstrakt ist, dass ihre Konzepte für die Programmierung so nützlich sind.
Es ist daher nicht verwunderlich, dass die Verallgemeinerung der Monaden, die wir unten präsentieren, auch einen engen Zusammenhang mit der Kategorietheorie hat. Wir betonen jedoch, dass unser Zweck sehr praktisch ist: Es geht nicht darum, die Kategorietheorie zu implementieren, sondern einen allgemeineren Weg zu finden, um Kombinatorbibliotheken zu strukturieren. Es ist einfach unser Glück, dass Mathematiker bereits einen Großteil der Arbeit für uns geleistet haben!
von der Verallgemeinerung von Monaden zu Pfeilen von John Hughes