Haskell Language
monaden
Zoeken…
Invoering
Een monade is een gegevenstype van configureerbare acties. Monad
is de klasse van type constructors waarvan de waarden dergelijke acties vertegenwoordigen. Misschien is IO
de meest herkenbare: een waarde van IO a
is een "recept voor het ophalen van een a
waarde uit de echte wereld".
We zeggen dat een typeconstructeur m
(zoals []
of Maybe
) een monade vormt als er een instance Monad m
voldoet aan bepaalde wetten over de samenstelling van acties. We kunnen dan over ma
redeneren als een "actie waarvan het resultaat type a
".
De misschien monade
Maybe
wordt gebruikt om mogelijk lege waarden weer te geven - vergelijkbaar met null
in andere talen. Gewoonlijk wordt het gebruikt als het uitvoertype van functies die op een of andere manier kunnen mislukken.
Overweeg de volgende functie:
halve :: Int -> Maybe Int
halve x
| even x = Just (x `div` 2)
| odd x = Nothing
Denk aan halve
als een actie, afhankelijk van een Int
, die het gehele getal probeert te halveren, als het niet vreemd is.
Hoe halve
we een geheel getal drie keer?
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
is een reeks van driehalve
stappen aan elkaar geketend. - Als een
halve
stap mislukt, willen we dat de hele compositietakeOneEighth
mislukt. - Als een
halve
stap slaagt, willen we het resultaat naar voren halen.
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
en nu kunnen we schrijven:
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 samenstelling <=<
wordt gedefinieerd als (g <=< f) x = g =<< fx
, of gelijkwaardig als (f >=> g) x = fx >>= g
. Hiermee wordt de bovenstaande definitie rechtvaardig
takeOneEighth :: Int -> Maybe Int
takeOneEighth = halve <=< halve <=< halve -- infixr 1 <=<
-- or, equivalently,
-- halve >=> halve >=> halve -- infixr 1 >=>
Er zijn drie monadenwetten die door elke monade moeten worden nageleefd, dat is elk type dat een voorbeeld is van de Monad
typeklasse:
1. return x >>= f = f x
2. m >>= return = m
3. (m >>= g) >>= h = m >>= (\y -> g y >>= h)
waar m
een monade is, heeft f
type a -> mb
en g
heeft type b -> mc
.
Of gelijkwaardig, met behulp van de hierboven gedefinieerde >=>
Kleisli-samenstellingsexploitant:
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 } }
Het gehoorzamen aan deze wetten maakt het een stuk eenvoudiger om over de monade te redeneren, omdat het garandeert dat het gebruik van monadische functies en het samenstellen ervan zich op een redelijke manier gedraagt, vergelijkbaar met andere monaden.
Laten we controleren of de Maybe
monade de drie monadenwetten gehoorzaamt.
- De linker identiteitswet -
return x >>= f = fx
return z >>= f
= (Just z) >>= f
= f z
- De juiste identiteitswet -
m >>= return = m
-
Just
data constructor
Just z >>= return
= return z
= Just z
-
Nothing
gegevensconstructeur
Nothing >>= return
= Nothing
- De associativiteitswet -
(m >>= f) >>= g = m >>= (\x -> fx >>= g)
-
Just
data constructor
-- 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
gegevensconstructor
-- Left-hand side
(Nothing >>= f) >>= g
= Nothing >>= g
= Nothing
-- Right-hand side
Nothing >>= (\x -> f x >>= g)
= Nothing
IO monade
Er is geen manier om een waarde van type a
krijgen uit een uitdrukking van type IO a
en dat zou er ook niet moeten zijn. Dit is eigenlijk een groot deel van de reden waarom monaden worden gebruikt om IO
te modelleren.
Een uitdrukking van type IO a
kan worden gezien als een actie die kan communiceren met de echte wereld en, indien uitgevoerd, zou resulteren in iets van type a
. De functie getLine :: IO String
uit de prelude betekent bijvoorbeeld niet dat er onder getLine
een specifieke string is die ik kan extraheren - het betekent dat getLine
de actie vertegenwoordigt van het verkrijgen van een lijn uit standaardinvoer.
Niet verrassend, main :: IO ()
omdat een Haskell-programma een berekening / actie vertegenwoordigt die in wisselwerking staat met de echte wereld.
De dingen die je kunt doen om uitdrukkingen van type IO a
omdat IO
een monade is:
Volg de volgorde van twee acties met
(>>)
om een nieuwe actie te produceren die de eerste actie uitvoert, de waarde verwijdert die wordt geproduceerd en vervolgens de tweede actie uitvoert.-- print the lines "Hello" then "World" to stdout putStrLn "Hello" >> putStrLn "World"
Soms wilt u de waarde die in de eerste actie is geproduceerd niet weggooien - u wilt eigenlijk dat deze in een tweede actie wordt ingevoerd. Daarvoor hebben we
>>=
. VoorIO
heeft het type(>>=) :: IO a -> (a -> IO b) -> IO b
.-- get a line from stdin and print it back out getLine >>= putStrLn
Neem een normale waarde en zet deze om in een actie die direct de waarde retourneert die u eraan hebt gegeven. Deze functie is minder uiteraard handig totdat u start met het gebruik
do
notatie.-- make an action that just returns 5 return 5
Meer van de Haskell Wiki op de IO-monade hier .
Lijst Monad
De lijsten vormen een monade. Ze hebben een monade-instantiatie gelijk aan deze:
instance Monad [] where return x = [x] xs >>= f = concat (map f xs)
We kunnen ze gebruiken om niet-determinisme in onze berekeningen na te bootsen. Wanneer we xs >>= f
, wordt de functie f :: a -> [b]
toegewezen aan de lijst xs
, waarbij een lijst met resultatenlijsten wordt verkregen van elke toepassing van f
over elk element van xs
, en alle lijsten met resultaten worden vervolgens samengevoegd tot één lijst met alle resultaten. Als voorbeeld berekenen we een som van twee niet-deterministische getallen met behulp van do-notatie , waarbij de som wordt voorgesteld door een lijst met sommen van alle paren van gehele getallen uit twee lijsten, waarbij elke lijst alle mogelijke waarden van een niet-deterministisch getal vertegenwoordigt:
sumnd xs ys = do
x <- xs
y <- ys
return (x + y)
Of equivalent, met behulp van liftM2
in Control.Monad
:
sumnd = liftM2 (+)
we verkrijgen:
> sumnd [1,2,3] [0,10]
[1,11,2,12,3,13]
Monad als een subklasse van Applicative
Vanaf GHC 7.10 is Applicative
een superklasse van Monad
(dwz elk type dat een Monad
moet ook een Applicative
). Alle methoden van Applicative
( pure
, <*>
) kunnen worden geïmplementeerd in termen van Monad
methoden ( return
, >>=
).
Het is duidelijk dat pure
en return
gelijkwaardige doelen dienen, dus pure = return
. De definitie voor <*>
is te relatief duidelijk:
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]
Deze functie is gedefinieerd als ap
in de standaardbibliotheken.
Dus als je al een instantie van Monad
voor een type hebt gedefinieerd, kun je effectief een instantie van Applicative
ervoor krijgen "gratis" door te definiëren
instance Applicative < type > where
pure = return
(<*>) = ap
Net als bij de monadewetten worden deze gelijkwaardigheid niet afgedwongen, maar ontwikkelaars moeten ervoor zorgen dat ze altijd worden nageleefd.
Geen algemene manier om waarde uit een monadische berekening te halen
U kunt waarden omzetten in acties en het resultaat van de ene berekening in een andere doorgeven:
return :: Monad m => a -> m a
(>>=) :: Monad m => m a -> (a -> m b) -> m b
De definitie van een monade garandeert echter niet het bestaan van een functie van het type Monad m => ma -> a
.
Dat betekent dat er over het algemeen geen manier is om een waarde uit een berekening te extraheren (dwz deze uit te pakken ). Dit is het geval voor veel gevallen:
extract :: Maybe a -> a
extract (Just x) = x -- Sure, this works, but...
extract Nothing = undefined -- We can’t extract a value from failure.
Specifiek is er geen functie IO a -> a
, die vaak beginners verwart; zie dit voorbeeld .
doe-notatie
do
-notatie is syntactische suiker voor monaden. Dit zijn de regels:
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
Deze definities zijn bijvoorbeeld gelijkwaardig:
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)
Definitie van Monad
class Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
De belangrijkste functie voor het omgaan met monaden is de bindoperator >>=
:
(>>=) :: m a -> (a -> m b) -> m b
- Denk aan
ma
als "een actie meta
resultaat". - Denk aan
a -> mb
“een actie (afhankelijk van een alsa
parameter) met eenb
. Result” .
>>=
reeksen twee acties samen door het resultaat van de eerste actie naar de tweede te sturen.
De andere functie gedefinieerd door Monad
is:
return :: a -> m a
De naam is jammer: deze return
heeft niets te maken met het return
sleutelwoord in imperatieve programmeertalen.
return x
is de triviale actie die x
als resultaat oplevert. (Het is triviaal in de volgende zin :)
return x >>= f ≡ f x -- “left identity” monad law
x >>= return ≡ x -- “right identity” monad law