Benjamin Pierce powiedział w TAPL
System typów można traktować jako obliczanie pewnego rodzaju statycznego przybliżenia zachowań terminów w programie w czasie wykonywania.
Dlatego język wyposażony w potężny system pisma jest bardziej wyrazisty niż język źle napisany. Możesz myśleć o monadach w ten sam sposób.
Jako @Carl i sigfpe point możesz wyposażyć typ danych we wszystkie operacje, które chcesz, bez uciekania się do monad, klas i innych abstrakcyjnych rzeczy. Monady pozwalają jednak nie tylko pisać kod wielokrotnego użytku, ale także usuwać wszystkie zbędne szczegóły.
Jako przykład załóżmy, że chcemy filtrować listę. Najprostszym sposobem jest użycie filterfunkcji filter (> 3) [1..10]:, która jest równa [4,5,6,7,8,9,10].
Nieco bardziej skomplikowana wersja filter, która również przechodzi przez akumulator od lewej do prawej, to
swap (x, y) = (y, x)
(.*) = (.) . (.)
filterAccum :: (a -> b -> (Bool, a)) -> a -> [b] -> [b]
filterAccum f a xs = [x | (x, True) <- zip xs $ snd $ mapAccumL (swap .* f) a xs]
Aby dostać wszystko i, tak, że i <= 10, sum [1..i] > 4, sum [1..i] < 25możemy napisać
filterAccum (\a x -> let a' = a + x in (a' > 4 && a' < 25, a')) 0 [1..10]
co jest równe [3,4,5,6].
Lub możemy przedefiniować nubfunkcję, która usuwa zduplikowane elementy z listy, pod względem filterAccum:
nub' = filterAccum (\a x -> (x `notElem` a, x:a)) []
nub' [1,2,4,5,4,3,1,8,9,4]równa [1,2,4,5,3,8,9]. Lista jest przekazywana tutaj jako akumulator. Kod działa, ponieważ możliwe jest opuszczenie listy monad, więc całe obliczenia pozostają czyste ( notElemwłaściwie nie używają >>=, ale mogą). Jednak nie jest możliwe bezpieczne opuszczenie monady IO (tzn. Nie można wykonać akcji IO i zwrócić czystej wartości - wartość zawsze będzie opakowana w monadę IO). Innym przykładem są zmienne tablice: po opuszczeniu monady ST, gdzie żyje zmienna tablica, nie można już aktualizować tablicy w stałym czasie. Potrzebujemy więc monadycznego filtrowania z Control.Monadmodułu:
filterM :: (Monad m) => (a -> m Bool) -> [a] -> m [a]
filterM _ [] = return []
filterM p (x:xs) = do
flg <- p x
ys <- filterM p xs
return (if flg then x:ys else ys)
filterMwykonuje akcję monadyczną dla wszystkich elementów z listy, uzyskując elementy, dla których powraca akcja monadyczna True.
Przykład filtrowania z tablicą:
nub' xs = runST $ do
arr <- newArray (1, 9) True :: ST s (STUArray s Int Bool)
let p i = readArray arr i <* writeArray arr i False
filterM p xs
main = print $ nub' [1,2,4,5,4,3,1,8,9,4]
drukuje [1,2,4,5,3,8,9]zgodnie z oczekiwaniami.
I wersja z monadą IO, która pyta, które elementy zwrócić:
main = filterM p [1,2,4,5] >>= print where
p i = putStrLn ("return " ++ show i ++ "?") *> readLn
Na przykład
return 1? -- output
True -- input
return 2?
False
return 4?
False
return 5?
True
[1,5] -- output
I jako ostateczną ilustrację filterAccummożna zdefiniować w kategoriach filterM:
filterAccum f a xs = evalState (filterM (state . flip f) xs) a
z StateTmonadą używaną pod maską, która jest zwykłym typem danych.
Ten przykład pokazuje, że monady pozwalają nie tylko na abstrakcyjny kontekst obliczeniowy i pisanie czystego kodu wielokrotnego użytku (ze względu na możliwość komponowania monad, jak wyjaśnia @Carl), ale także na równomierne traktowanie typów danych i wbudowanych prymitywów.