Szukaj…


Wprowadzenie

Monada to typ danych możliwych do skomponowania działań. Monad to klasa konstruktorów typów, których wartości reprezentują takie akcje. Może IO jest najbardziej rozpoznawalny jeden: wartość IO a to „recepta na zdobycie się a wartość od realnego świata”.

Mówimy, że konstruktor typów m (taki jak [] lub Maybe ) tworzy monadę, jeśli istnieje instance Monad m spełniająca określone prawa dotyczące kompozycji działań. Możemy wtedy rozumować ma jako „akcję, której wynik ma typ a ”.

Może monada

Maybe być używany do reprezentowania możliwie pustych wartości - podobnych do null w innych językach. Zwykle jest używany jako typ wyjściowy funkcji, które mogą w jakiś sposób zawieść.

Rozważ następującą funkcję:

halve :: Int -> Maybe Int
halve x
  | even x = Just (x `div` 2)
  | odd x  = Nothing

Pomyśl o halve jako akcji, w zależności od Int , która próbuje zmniejszyć liczbę całkowitą o połowę, ale jeśli jest nieparzysta, kończy się niepowodzeniem.

Jak trzy razy halve liczbę całkowitą o halve ?

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 to sekwencja trzech kroków o halve połączonych ze sobą.
  • Jeśli krok o halve nie powiedzie, chcemy, aby cała kompozycja takeOneEighth .
  • Jeśli krok o halve powiedzie, chcemy przesłać jego wynik do przodu.

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

a teraz możemy napisać:

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

Skład Kleisli <=< jest zdefiniowany jako (g <=< f) x = g =<< fx lub równoważnie jako (f >=> g) x = fx >>= g . Dzięki niemu powyższa definicja staje się sprawiedliwa

takeOneEighth :: Int -> Maybe Int
takeOneEighth = halve <=< halve <=< halve               -- infixr 1 <=<
        -- or, equivalently,                    
        --      halve >=> halve >=> halve               -- infixr 1 >=>    

Istnieją trzy prawa monad, których powinna przestrzegać każda monada, to znaczy każdy typ, który jest instancją klasy Monad :

1.  return x >>= f  =  f x
2.    m >>= return  =  m
3. (m >>= g) >>= h  =  m >>= (\y -> g y >>= h)

gdzie m jest monadą, f ma typ a -> mb a g ma typ b -> mc .

Lub równoważnie, stosując >=> Kleisli operatora kompozycję określoną powyżej:

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 } }

Przestrzeganie tych praw znacznie ułatwia rozumowanie monady, ponieważ gwarantuje, że używanie funkcji monadycznych i ich komponowanie zachowuje się w rozsądny sposób, podobnie jak w przypadku innych monad.

Sprawdźmy, czy monada Maybe przestrzega trzech praw monady.

  1. Lewe prawo tożsamości - return x >>= f = fx
return z >>= f 
= (Just z) >>= f 
= f z
  1. Właściwe prawo tożsamości - m >>= return = m
  • Just konstruktor danych
Just z >>= return
= return z
= Just z  
  • Nothing konstruktora danych
Nothing >>= return
= Nothing 
  1. Prawo asocjacyjne - (m >>= f) >>= g = m >>= (\x -> fx >>= g)
  • Just konstruktor danych
-- 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 konstruktora danych
-- Left-hand side
(Nothing >>= f) >>= g
= Nothing >>= g
= Nothing

-- Right-hand side
Nothing >>= (\x -> f x >>= g)
= Nothing

Monada IO

Nie ma sposobu na uzyskanie wartości typu a z wyrażenia typu IO a i nie powinno być. Jest to tak naprawdę duża część tego, dlaczego monady są używane do modelowania IO .

Wyrażenie typu IO a może być traktowane jako reprezentujące działanie, które może wchodzić w interakcje ze światem rzeczywistym, a jeśli zostanie wykonane, spowoduje coś typu a . Na przykład funkcja getLine :: IO String z preludium nie oznacza, że pod getLine jest jakiś określony ciąg, który mogę wyodrębnić - oznacza to, że getLine reprezentuje działanie polegające na uzyskaniu linii ze standardowego wejścia.

Nic dziwnego, że main :: IO () ponieważ program Haskell reprezentuje obliczenia / działania, które współdziałają ze światem rzeczywistym.

Rzeczy, które możesz zrobić z wyrażeniami typu IO a ponieważ IO to monada:

  • Wykonaj dwie akcje za pomocą (>>) aby utworzyć nową akcję, która wykonuje pierwszą akcję, odrzuca wszelką wytworzoną wartość, a następnie wykonuje drugą akcję.

      -- print the lines "Hello" then "World" to stdout
      putStrLn "Hello" >> putStrLn "World"
    
  • Czasami nie chcesz odrzucić wartości, która została wygenerowana w pierwszej akcji - tak naprawdę chciałbyś, aby została wykorzystana w drugiej akcji. Do tego mamy >>= . Dla IO ma typ (>>=) :: IO a -> (a -> IO b) -> IO b .

     -- get a line from stdin and print it back out
     getLine >>= putStrLn
    
  • Weź normalną wartość i przekonwertuj ją na działanie, które natychmiast zwraca wartość, którą jej podałeś. Funkcja ta jest mniej przydatna oczywiście aż zaczniesz używać do notacji.

     -- make an action that just returns 5
     return 5
    

Więcej z Wiki Haskell na monadzie IO tutaj .

Lista Monada

Listy tworzą monadę. Mają instancję monady odpowiadającą tej:

instance Monad [] where 
  return x = [x]
  xs >>= f = concat (map f xs)               

Możemy ich użyć do naśladowania niedeterminizmu w naszych obliczeniach. Kiedy używamy xs >>= f , funkcja f :: a -> [b] jest mapowana na listę xs , uzyskując listę list wyników każdej aplikacji f na każdym elemencie xs i wszystkich listach wyniki są następnie łączone w jedną listę wszystkich wyników. Jako przykład obliczamy sumę dwóch liczb niedeterministycznych przy użyciu notacji , przy czym suma jest reprezentowana przez listę sum wszystkich par liczb całkowitych z dwóch list, przy czym każda lista reprezentuje wszystkie możliwe wartości liczby niedeterministycznej:

sumnd xs ys = do
  x <- xs
  y <- ys
  return (x + y)

Lub równoważnie, używając liftM2 w Control.Monad :

sumnd = liftM2 (+)

otrzymujemy:

> sumnd [1,2,3] [0,10]
[1,11,2,12,3,13]

Monada jako podklasa aplikacyjna

Począwszy od GHC 7.10, Applicative jest nadklasą Monad (tzn. Każdy typ, który jest Monad musi być także Applicative ). Wszystkie metody Applicative ( pure , <*> ) można zaimplementować w kategoriach metod Monad ( return , >>= ).

Oczywiste jest, że pure i return służą równoważnym celom, więc pure = return . Definicja <*> jest zbyt jasna:

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]                   

Ta funkcja jest zdefiniowana jako ap w standardowych bibliotekach.

Dlatego jeśli już zdefiniowałeś wystąpienie Monad dla typu, możesz skutecznie uzyskać wystąpienie Applicative dla niego „za darmo” poprzez zdefiniowanie

instance Applicative < type > where
    pure  = return
    (<*>) = ap

Podobnie jak w przypadku praw monady, te równoważności nie są egzekwowane, ale programiści powinni upewnić się, że są zawsze przestrzegani.

Nie ma ogólnego sposobu na wyodrębnienie wartości z obliczeń monadycznych

Możesz zawijać wartości w akcje i przesyłać wyniki jednego obliczenia do drugiego:

return :: Monad m => a -> m a
(>>=)  :: Monad m => m a -> (a -> m b) -> m b

Jednak definicja Monady nie gwarantuje istnienia funkcji typu Monad m => ma -> a .

Oznacza to, że generalnie nie ma możliwości wyodrębnienia wartości z obliczeń (tj. „Rozpakowanie”). Dzieje się tak w wielu przypadkach:

extract :: Maybe a -> a
extract (Just x) = x          -- Sure, this works, but...
extract Nothing  = undefined  -- We can’t extract a value from failure.

W szczególności nie ma funkcji IO a -> a , która często myli początkujących; zobacz ten przykład .

notacja

do -notation to cukier składniowy dla monad. Oto zasady:

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

Na przykład te definicje są równoważne:

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)

Definicja Monady

class Monad m where
    return :: a -> m a
    (>>=) :: m a -> (a -> m b) -> m b

Najważniejszą funkcją do obsługi monad jest operator bind >>= :

(>>=) :: m a -> (a -> m b) -> m b
  • Pomyśl ma jako „działania ze a wyniku”.
  • Pomyśl a -> mb „działanie (w zależności na jak a parametrze) z b . Wynik” .

>>= sekwencjonuje dwie akcje razem, przesyłając wyniki z pierwszej akcji do drugiej.

Inną funkcją zdefiniowaną przez Monad jest:

return :: a -> m a

Jego nazwa jest niefortunna: ten return nie ma nic wspólnego ze słowem kluczowym return znalezionym w imperatywnych językach programowania.

return x jest trywialną akcją dającą x jako wynik. (Jest to trywialne w następującym znaczeniu :)

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
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow