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 drie halve stappen aan elkaar geketend.
  • Als een halve stap mislukt, willen we dat de hele compositie takeOneEighth 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.

  1. De linker identiteitswet - return x >>= f = fx
return z >>= f 
= (Just z) >>= f 
= f z
  1. De juiste identiteitswet - m >>= return = m
  • Just data constructor
Just z >>= return
= return z
= Just z  
  • Nothing gegevensconstructeur
Nothing >>= return
= Nothing 
  1. 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 >>= . Voor IO 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 met a resultaat".
  • Denk aan a -> mb “een actie (afhankelijk van een als a parameter) met een b . 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


Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow