Haskell Language
Monady
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 }
-
takeOneEighthto sekwencja trzech kroków ohalvepołączonych ze sobą. - Jeśli krok o
halvenie powiedzie, chcemy, aby cała kompozycjatakeOneEighth. - Jeśli krok o
halvepowiedzie, 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.
- Lewe prawo tożsamości -
return x >>= f = fx
return z >>= f
= (Just z) >>= f
= f z
- Właściwe prawo tożsamości -
m >>= return = m
-
Justkonstruktor danych
Just z >>= return
= return z
= Just z
-
Nothingkonstruktora danych
Nothing >>= return
= Nothing
- Prawo asocjacyjne -
(m >>= f) >>= g = m >>= (\x -> fx >>= g)
-
Justkonstruktor 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
-
Nothingkonstruktora 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
>>=. DlaIOma typ(>>=) :: IO a -> (a -> IO b) -> IO b.-- get a line from stdin and print it back out getLine >>= putStrLnWeź 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ć
donotacji.-- 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
majako „działania zeawyniku”. - Pomyśl
a -> mb„działanie (w zależności na jakaparametrze) zb. 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