Haskell Language
Monaden
Suche…
Einführung
Eine Monade ist ein Datentyp zusammenstellbarer Aktionen. Monad
ist die Klasse von Typkonstruktoren, deren Werte solche Aktionen darstellen. Vielleicht ist IO
das am besten erkennbare: Ein Wert von IO a
ist ein "Rezept zum Abrufen a
Wertes aus der realen Welt".
Wir sagen, ein Typkonstruktor m
(wie []
oder Maybe
) bildet eine Monade, wenn es eine instance Monad m
gibt, die bestimmte Gesetze über die Zusammenstellung von Aktionen erfüllt. Wir können dann über ma
als eine "Aktion, deren Ergebnis den Typ a
" denken.
Die vielleicht Monade
Maybe
verwendet wird möglicherweise leere Werte darzustellen - ähnlich wie null
in anderen Sprachen. Normalerweise wird es als Ausgabetyp von Funktionen verwendet, die auf irgendeine Weise versagen können.
Betrachten Sie die folgende Funktion:
halve :: Int -> Maybe Int
halve x
| even x = Just (x `div` 2)
| odd x = Nothing
Stellen Sie sich halve
als eine Aktion vor, die von einem Int
, das versucht, die ganze Zahl zu halbieren, und versagt, wenn sie ungerade ist.
Wie halve
wir eine ganze Zahl dreimal?
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
ist eine Abfolge von dreihalve
Schritten, die miteinander verkettet sind. - Wenn ein
halve
fehlschlägt, möchten wir, dass die gesamte KompositiontakeOneEighth
fehlschlägt. - Wenn eine
halve
Stufe erfolgreich ist, möchten wir das Ergebnis weiterleiten.
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
und jetzt können wir schreiben:
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
Kleisli-Zusammensetzung <=<
ist definiert als (g <=< f) x = g =<< fx
oder äquivalent als (f >=> g) x = fx >>= g
. Damit wird die obige Definition gerecht
takeOneEighth :: Int -> Maybe Int
takeOneEighth = halve <=< halve <=< halve -- infixr 1 <=<
-- or, equivalently,
-- halve >=> halve >=> halve -- infixr 1 >=>
Es gibt drei Monadengesetze, die von jeder Monade befolgt werden sollten, dh jeder Typ, der eine Instanz der Monad
Typklasse darstellt:
1. return x >>= f = f x
2. m >>= return = m
3. (m >>= g) >>= h = m >>= (\y -> g y >>= h)
wobei m
eine Monade ist, hat f
den Typ a -> mb
und g
den Typ b -> mc
.
Oder gleichwertig mit dem oben definierten Kompositionsoperator >=>
Kleisli:
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 } }
Das Befolgen dieser Gesetze macht es viel einfacher, über die Monade zu argumentieren, weil sie garantiert, dass die Verwendung monadischer Funktionen und das Komponieren dieser Funktionen sich auf vernünftige Weise verhalten, ähnlich wie andere Monaden.
Lassen Sie uns prüfen, ob die Maybe
Monade den drei Monadengesetzen gehorcht.
- Das linke Identitätsgesetz -
return x >>= f = fx
return z >>= f
= (Just z) >>= f
= f z
- Das richtige Identitätsgesetz -
m >>= return = m
-
Just
Datenkonstruktor
Just z >>= return
= return z
= Just z
-
Nothing
Datenkonstruktor
Nothing >>= return
= Nothing
- Das Assoziativitätsgesetz -
(m >>= f) >>= g = m >>= (\x -> fx >>= g)
-
Just
Datenkonstruktor
-- 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
-
Nothing
Datenkonstruktor
-- Left-hand side
(Nothing >>= f) >>= g
= Nothing >>= g
= Nothing
-- Right-hand side
Nothing >>= (\x -> f x >>= g)
= Nothing
IO Monade
Es gibt keine Möglichkeit, einen Wert des Typs a
aus einem Ausdruck des Typs IO a
und dies sollte nicht der Fall sein. Dies ist eigentlich ein großer Teil des Grunds, warum Monaden zum Modellieren von IO
.
Ein Ausdruck des Typs IO a
kann als eine Aktion bezeichnet werden, die mit der realen Welt interagieren kann und, wenn sie ausgeführt wird, zu etwas vom Typ a
. Zum Beispiel bedeutet die Funktion getLine :: IO String
aus dem Vorspiel nicht, dass sich unter getLine
eine bestimmte Zeichenfolge befindet, die ich extrahieren kann. Dies bedeutet, dass getLine
die Aktion darstellt, bei der eine Zeile aus der Standardeingabe abgerufen wird.
Es überrascht nicht, dass main :: IO ()
da ein Haskell-Programm eine Berechnung / Aktion darstellt, die mit der realen Welt interagiert.
Die Dinge können Sie Ausdrücke vom Typ tun IO a
, weil IO
Monade ist:
Folgen Sie zwei Aktionen mit
(>>)
, um eine neue Aktion zu erzeugen, die die erste Aktion ausführt, den von ihr erzeugten Wert verwirft und anschließend die zweite Aktion ausführt.-- print the lines "Hello" then "World" to stdout putStrLn "Hello" >> putStrLn "World"
Manchmal möchten Sie den Wert, der in der ersten Aktion erzeugt wurde, nicht verwerfen. Sie möchten, dass er tatsächlich in eine zweite Aktion eingespeist wird. Dafür haben wir
>>=
. FürIO
hat es den Typ(>>=) :: IO a -> (a -> IO b) -> IO b
.-- get a line from stdin and print it back out getLine >>= putStrLn
Nehmen Sie einen normalen Wert und konvertieren Sie ihn in eine Aktion, die den von Ihnen angegebenen Wert sofort zurückgibt. Diese Funktion ist weniger offensichtlich nützlich , bis Sie beginnen
do
Notation.-- make an action that just returns 5 return 5
Mehr aus dem Haskell-Wiki zur IO-Monade hier .
Liste Monade
Die Listen bilden eine Monade. Sie haben eine monadische Instanziierung, die dieser entspricht:
instance Monad [] where return x = [x] xs >>= f = concat (map f xs)
Wir können sie verwenden, um Nicht-Determinismus in unseren Berechnungen nachzuahmen. Wenn wir xs >>= f
, wird die Funktion f :: a -> [b]
über die Liste xs
abgebildet, wobei eine Liste mit Ergebnislisten jeder Anwendung von f
über jedes Element von xs
und alle Listen von erhalten wird Die Ergebnisse werden dann in einer Liste aller Ergebnisse zusammengefasst. Als Beispiel berechnen wir eine Summe aus zwei nicht-deterministischen Zahlen unter Verwendung der Schreibweise , wobei die Summe durch eine Liste von Summen aller Paare von Ganzzahlen aus zwei Listen dargestellt wird, wobei jede Liste alle möglichen Werte einer nicht-deterministischen Zahl darstellt:
sumnd xs ys = do
x <- xs
y <- ys
return (x + y)
Oder gleichwertig mit liftM2
in Control.Monad
:
sumnd = liftM2 (+)
wir erhalten:
> sumnd [1,2,3] [0,10]
[1,11,2,12,3,13]
Monade als Unterklasse von Applicative
Ab GHC 7.10 ist Applicative
eine Oberklasse von Monad
(dh jeder Typ, der eine Monad
muss auch ein Applicative
). Alle Methoden von Applicative
( pure
, <*>
) können als Methoden von Monad
( return
, >>=
) implementiert werden.
Es ist offensichtlich, dass pure
und return
gleichwertige Zwecke haben, also pure = return
. Die Definition für <*>
ist zu relativ klar:
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]
Diese Funktion ist in den Standardbibliotheken als ap
definiert.
Wenn Sie also bereits eine Instanz von Monad
für einen Typ definiert haben, können Sie eine Instanz von Applicative
für ihn "kostenlos" erhalten, indem Sie sie definieren
instance Applicative < type > where
pure = return
(<*>) = ap
Wie bei den Monadengesetzen werden diese Gleichwertigkeiten nicht durchgesetzt, aber die Entwickler sollten sicherstellen, dass sie immer eingehalten werden.
Kein allgemeiner Weg, um Wert aus einer monadischen Berechnung zu ziehen
Sie können Werte in Aktionen umschließen und das Ergebnis einer Berechnung in eine andere umleiten:
return :: Monad m => a -> m a
(>>=) :: Monad m => m a -> (a -> m b) -> m b
Die Definition einer Monade garantiert jedoch nicht die Existenz einer Funktion des Typs Monad m => ma -> a
.
Das bedeutet, dass es im Allgemeinen keine Möglichkeit gibt, einen Wert aus einer Berechnung zu extrahieren (dh ihn "auspacken"). Dies ist in vielen Fällen der Fall:
extract :: Maybe a -> a
extract (Just x) = x -- Sure, this works, but...
extract Nothing = undefined -- We can’t extract a value from failure.
Insbesondere gibt es keine Funktion IO a -> a
, die Anfänger oft verwirrt; siehe dieses Beispiel .
Notation
do
notation ist syntaktischer Zucker für Monaden. Hier sind die Regeln:
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
Zum Beispiel sind diese Definitionen gleichwertig:
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)
Definition von Monade
class Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
Die wichtigste Funktion im Umgang mit Monaden ist der Bind-Operator >>=
:
(>>=) :: m a -> (a -> m b) -> m b
- Stellen Sie sich
ma
als "eine Aktion mita
Ergebnis" vor . - Stellen Sie sich
a -> mb
als "eine Aktion (abhängig vona
Parameter) mit einemb
Ergebnis" vor .
>>=
sequenziert zwei Aktionen zusammen, indem das Ergebnis von der ersten Aktion zur zweiten weitergeleitet wird.
Die andere von Monad
definierte Funktion ist:
return :: a -> m a
Der Name ist unglücklich: Diese return
hat nichts mit dem in imperativen Programmiersprachen gefundenen return
Schlüsselwort zu tun.
return x
ist die triviale Aktion, die als Ergebnis x
ergibt. (Es ist im folgenden Sinne trivial :)
return x >>= f ≡ f x -- “left identity” monad law
x >>= return ≡ x -- “right identity” monad law