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 filter
funkcji 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] < 25
moż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ć nub
funkcję, 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 ( notElem
wł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.Monad
moduł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)
filterM
wykonuje 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ę filterAccum
można zdefiniować w kategoriach filterM
:
filterAccum f a xs = evalState (filterM (state . flip f) xs) a
z StateT
monadą 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.