Benjamin Pierce berkata dalam TAPL
Suatu sistem tipe dapat dianggap sebagai penghitungan semacam perkiraan statis terhadap perilaku run-time dari istilah dalam suatu program.
Karena itulah bahasa yang dilengkapi dengan sistem tipe yang kuat lebih ekspresif daripada bahasa yang diketik dengan buruk. Anda dapat berpikir tentang monad dengan cara yang sama.
Sebagai @Carl dan sigfpe point, Anda dapat melengkapi datatype dengan semua operasi yang Anda inginkan tanpa menggunakan monad, typeclasses atau apa pun hal abstrak lainnya. Namun monad memungkinkan Anda tidak hanya untuk menulis kode yang dapat digunakan kembali, tetapi juga untuk abstrak semua detail yang berlebihan.
Sebagai contoh, katakanlah kita ingin memfilter daftar. Cara paling sederhana adalah dengan menggunakan filter
fungsi filter (> 3) [1..10]
:, yang sama dengan [4,5,6,7,8,9,10]
.
Versi yang sedikit lebih rumit filter
, yang juga meneruskan akumulator dari kiri ke kanan, adalah
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]
Untuk mendapatkan semuanya i
, seperti itu i <= 10, sum [1..i] > 4, sum [1..i] < 25
, kita bisa menulis
filterAccum (\a x -> let a' = a + x in (a' > 4 && a' < 25, a')) 0 [1..10]
yang sama dengan [3,4,5,6]
.
Atau kita dapat mendefinisikan kembali nub
fungsi, yang menghapus elemen duplikat dari daftar, dalam hal filterAccum
:
nub' = filterAccum (\a x -> (x `notElem` a, x:a)) []
nub' [1,2,4,5,4,3,1,8,9,4]
sama dengan [1,2,4,5,3,8,9]
. Daftar dilewatkan sebagai akumulator di sini. Kode berfungsi, karena dimungkinkan untuk meninggalkan daftar monad, sehingga seluruh perhitungan tetap murni ( notElem
sebenarnya tidak digunakan >>=
, tetapi bisa). Namun tidak mungkin untuk meninggalkan mono IO dengan aman (yaitu Anda tidak dapat menjalankan aksi IO dan mengembalikan nilai murni - nilai selalu akan terbungkus dalam mono IO). Contoh lain adalah array yang dapat diubah: setelah Anda meninggalkan ST monad, tempat array yang dapat diubah tinggal, Anda tidak dapat memperbarui array dalam waktu yang konstan lagi. Jadi kita membutuhkan pemfilteran monadik dari Control.Monad
modul:
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
mengeksekusi aksi monadik untuk semua elemen dari daftar, menghasilkan elemen, untuk mana tindakan monadik kembali True
.
Contoh pemfilteran dengan larik:
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]
mencetak [1,2,4,5,3,8,9]
seperti yang diharapkan.
Dan versi dengan IO monad, yang menanyakan elemen apa yang harus dikembalikan:
main = filterM p [1,2,4,5] >>= print where
p i = putStrLn ("return " ++ show i ++ "?") *> readLn
Misalnya
return 1? -- output
True -- input
return 2?
False
return 4?
False
return 5?
True
[1,5] -- output
Dan sebagai ilustrasi terakhir, filterAccum
dapat didefinisikan dalam hal filterM
:
filterAccum f a xs = evalState (filterM (state . flip f) xs) a
dengan StateT
monad, yang digunakan di bawah tenda, menjadi hanya tipe data biasa.
Contoh ini menggambarkan, bahwa monad tidak hanya memungkinkan Anda untuk abstrak konteks komputasi dan menulis kode yang dapat digunakan kembali bersih (karena kompabilitas monads, seperti @Carl menjelaskan), tetapi juga untuk memperlakukan tipe data yang ditentukan pengguna dan primitif bawaan secara seragam.