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 trehalvesteg kedjade ihop. - Om ett
halvesteg misslyckas, vill vi att hela kompositionentakeOneEighthmisslyckas. - Om ett
halvelyckas 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
-
Justdatakonstruktör
Just z >>= return
= return z
= Just z
-
Nothingdatakonstruktör
Nothing >>= return
= Nothing
- Associativitetslagen -
(m >>= f) >>= g = m >>= (\x -> fx >>= g)
-
Justdatakonstruktö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
-
Nothingdatakonstruktö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örIOhar den typen(>>=) :: IO a -> (a -> IO b) -> IO b.-- get a line from stdin and print it back out getLine >>= putStrLnTa 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
donotation.-- 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å
masom "en handling medaresultat" . - Tänk på
a -> mbsom "en åtgärd (beroende påaparameter) med ettbresultat." .
>>= 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