Haskell Language
monader
Sök…
Introduktion
En monad är en datatyp för komponerbara åtgärder. Monad
är klassen av typkonstruktörer vars värden representerar sådana handlingar. Kanske är IO
den mest igenkända: ett värde på IO a
är ett "recept för att hämta ett a
värde från den verkliga världen".
Vi säger att en typkonstruktör m
(som []
eller Maybe
) bildar en monad om det finns en instance Monad m
uppfyller vissa lagar om handlingens sammansättning. Vi kan då resonera om ma
som en "handling vars resultat har typ a
".
Den kanske monaden
Maybe
används för att representera möjliga tomma värden - liknande null
på andra språk. Vanligtvis används den som utgångstyp för funktioner som kan misslyckas på något sätt.
Tänk på följande funktion:
halve :: Int -> Maybe Int
halve x
| even x = Just (x `div` 2)
| odd x = Nothing
Tänk på halve
som en åtgärd, beroende på ett Int
, som försöker halvera heltalet, och misslyckas om det är udda.
Hur kan vi halve
ett heltal tre gånger?
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
är en sekvens av trehalve
steg kedjade ihop. - Om ett
halve
steg misslyckas, vill vi att hela kompositionentakeOneEighth
misslyckas. - Om ett
halve
lyckas vill vi leda resultatet framåt.
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
och nu kan vi skriva:
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-sammansättningen <=<
definieras som (g <=< f) x = g =<< fx
, eller motsvarande som (f >=> g) x = fx >>= g
. Med det blir definitionen ovan rättvis
takeOneEighth :: Int -> Maybe Int
takeOneEighth = halve <=< halve <=< halve -- infixr 1 <=<
-- or, equivalently,
-- halve >=> halve >=> halve -- infixr 1 >=>
Det finns tre monadslagar som bör följas av varje monad, det vill säga varje typ som är ett exempel på Monad
typkategorin:
1. return x >>= f = f x
2. m >>= return = m
3. (m >>= g) >>= h = m >>= (\y -> g y >>= h)
där m
är en monad, har f
typ a -> mb
och g
har typ b -> mc
.
Eller på motsvarande sätt med hjälp av >=>
Kleisli-sammansättningsoperatören definierad ovan:
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 } }
Att följa dessa lagar gör det mycket lättare att resonera om monaden, eftersom det garanterar att användning av monadiska funktioner och kompositioner uppför sig på ett rimligt sätt, liknande andra monader.
Låt oss kontrollera om monaden Maybe
följer de tre monadlagarna.
- Den vänstra identitetslagen -
return x >>= f = fx
return z >>= f
= (Just z) >>= f
= f z
- Rätt identitetslag -
m >>= return = m
-
Just
datakonstruktör
Just z >>= return
= return z
= Just z
-
Nothing
datakonstruktör
Nothing >>= return
= Nothing
- Associativitetslagen -
(m >>= f) >>= g = m >>= (\x -> fx >>= g)
-
Just
datakonstruktör
-- 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
datakonstruktör
-- Left-hand side
(Nothing >>= f) >>= g
= Nothing >>= g
= Nothing
-- Right-hand side
Nothing >>= (\x -> f x >>= g)
= Nothing
IO monad
Det finns inget sätt att få ett värde på typ a
ur ett uttryck av typ IO a
och det borde inte vara det. Detta är faktiskt en stor del av varför monader används för att modell IO
.
Ett uttryck av typ IO a
kan betraktas som att representera en handling som kan interagera med den verkliga världen och, om den utförs, skulle resultera i något av typ a
. Exempelvis getLine :: IO String
funktionen getLine :: IO String
från förspel inte att under getLine
finns det någon specifik sträng som jag kan extrahera - det betyder att getLine
representerar åtgärden att få en linje från standardinmatning.
Inte förvånande, main :: IO ()
eftersom ett Haskell-program representerar en beräkning / handling som interagerar med den verkliga världen.
De saker du kan göra för att uttrycka av typ IO a
eftersom IO
är en monad:
Följ två åtgärder med hjälp av
(>>)
att producera en ny åtgärd som utför den första åtgärden, kasserar det värde som den producerade och kör sedan den andra åtgärden.-- print the lines "Hello" then "World" to stdout putStrLn "Hello" >> putStrLn "World"
Ibland vill du inte kasta bort värdet som producerades i den första åtgärden - du skulle faktiskt vilja att det matas in i en andra åtgärd. För det har vi
>>=
. FörIO
har den typen(>>=) :: IO a -> (a -> IO b) -> IO b
.-- get a line from stdin and print it back out getLine >>= putStrLn
Ta ett normalt värde och konvertera det till en åtgärd som bara omedelbart returnerar det värde du gav det. Denna funktion är mindre uppenbart användbar förrän du börjar använda
do
notation.-- make an action that just returns 5 return 5
Mer från Haskell Wiki på IO-monaden här .
Lista Monad
Listorna bildar en monad. De har en monadinstans motsvarande den här:
instance Monad [] where return x = [x] xs >>= f = concat (map f xs)
Vi kan använda dem för att emulera icke-determinism i våra beräkningar. När vi använder xs >>= f
, mappas funktionen f :: a -> [b]
över listan xs
, vilket får en lista med resultatlistor för varje tillämpning av f
över varje element av xs
, och alla listor med resultaten sammanförs sedan till en lista med alla resultat. Som exempel beräknar vi en summa av två icke-deterministiska nummer med hjälp av do-notation , varvid summan representeras av listan med summor för alla par heltal från två listor, varje lista representerar alla möjliga värden för ett icke-deterministiskt nummer:
sumnd xs ys = do
x <- xs
y <- ys
return (x + y)
Eller på motsvarande sätt med hjälp av liftM2
i Control.Monad
:
sumnd = liftM2 (+)
vi får:
> sumnd [1,2,3] [0,10]
[1,11,2,12,3,13]
Monad som underklass för tillämpligt
Från GHC 7.10 är Applicative
en superklass av Monad
(dvs varje typ som är en Monad
måste också vara en Applicative
). Alla metoder för Applicative
( pure
, <*>
) kan implementeras i termer av metoder för Monad
( return
, >>=
).
Det är uppenbart att pure
och return
tjänar likvärdiga syften, så pure = return
. Definitionen för <*>
är för relativt 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]
Denna funktion definieras som ap
i standardbibliotek.
Så om du redan har definierat en instans av Monad
för en typ, kan du effektivt få en instans av Applicative
för det "gratis" genom att definiera
instance Applicative < type > where
pure = return
(<*>) = ap
Liksom med monadlagarna tillämpas inte dessa likvärdigheter, men utvecklare bör se till att de alltid upprätthålls.
Inget generellt sätt att utvinna värde från en monadisk beräkning
Du kan lägga in värden i handlingar och leda resultatet av en beräkning till en annan:
return :: Monad m => a -> m a
(>>=) :: Monad m => m a -> (a -> m b) -> m b
Definitionen av en Monad garanterar dock inte att det finns en funktion av typen Monad m => ma -> a
.
Det betyder att det i allmänhet inte finns något sätt att extrahera ett värde från en beräkning (dvs. "ta bort det"). Detta är fallet i många fall:
extract :: Maybe a -> a
extract (Just x) = x -- Sure, this works, but...
extract Nothing = undefined -- We can’t extract a value from failure.
Specifikt finns det ingen funktion IO a -> a
, som ofta förvirrar nybörjare; se detta exempel .
gör-notation
do
notation är syntaktiskt socker för monader. Här är reglerna:
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
Till exempel är dessa definitioner ekvivalenta:
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 av Monad
class Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
Den viktigaste funktionen för att hantera monader är bindoperatören >>=
:
(>>=) :: m a -> (a -> m b) -> m b
- Tänk på
ma
som "en handling meda
resultat" . - Tänk på
a -> mb
som "en åtgärd (beroende påa
parameter) med ettb
resultat." .
>>=
sekvenserar två åtgärder tillsammans genom att leda resultatet från den första åtgärden till den andra.
Den andra funktionen som definieras av Monad
är:
return :: a -> m a
Namnet är olyckligt: denna return
har ingenting att göra med det return
nyckelordet som finns i nödvändiga programmeringsspråk.
return x
är den triviala åtgärden som ger x
som resultat. (Det är trivialt i följande mening :)
return x >>= f ≡ f x -- “left identity” monad law
x >>= return ≡ x -- “right identity” monad law