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 tre halve steg kedjade ihop.
  • Om ett halve steg misslyckas, vill vi att hela kompositionen takeOneEighth 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.

  1. Den vänstra identitetslagen - return x >>= f = fx
return z >>= f 
= (Just z) >>= f 
= f z
  1. Rätt identitetslag - m >>= return = m
  • Just datakonstruktör
Just z >>= return
= return z
= Just z  
  • Nothing datakonstruktör
Nothing >>= return
= Nothing 
  1. 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ör IO 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 med a resultat" .
  • Tänk på a -> mb som "en åtgärd (beroende på a parameter) med ett b 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


Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow