Paket Control.Monad.Writer
tidak mengekspor konstruktor data Writer
. Saya rasa ini berbeda ketika LYAH ditulis.
Menggunakan kelas tipe MonadWriter di ghci
Sebaliknya, Anda membuat penulis menggunakan writer
fungsi tersebut. Misalnya, dalam sesi ghci yang bisa saya lakukan
ghci> import Control.Monad.Writer
ghci> let logNumber x = writer (x, ["Got number: " ++ show x])
Sekarang logNumber
adalah fungsi yang menciptakan penulis. Saya bisa menanyakan tipenya:
ghci> :t logNumber
logNumber :: (Show a, MonadWriter [String] m) => a -> m a
Yang memberi tahu saya bahwa tipe yang disimpulkan bukanlah fungsi yang mengembalikan penulis tertentu , melainkan apa pun yang mengimplementasikan MonadWriter
kelas tipe. Saya sekarang dapat menggunakannya:
ghci> let multWithLog = do { a <- logNumber 3; b <- logNumber 5; return (a*b) }
:: Writer [String] Int
(Input sebenarnya memasukkan semua dalam satu baris). Di sini saya telah menentukan tipe multWithLog
menjadi Writer [String] Int
. Sekarang saya bisa menjalankannya:
ghci> runWriter multWithLog
(15, ["Got number: 3","Got number: 5"])
Dan Anda melihat bahwa kami mencatat semua operasi perantara.
Mengapa kode ditulis seperti ini?
Mengapa repot-repot membuat MonadWriter
kelas tipe? Alasannya ada hubungannya dengan trafo monad. Seperti yang Anda sadari dengan benar, cara termudah untuk mengimplementasikan Writer
adalah sebagai pembungkus tipe baru di atas pasangan:
newtype Writer w a = Writer { runWriter :: (a,w) }
Anda dapat mendeklarasikan instance monad untuk ini, lalu menulis fungsinya
tell :: Monoid w => w -> Writer w ()
yang hanya mencatat masukannya. Sekarang misalkan Anda menginginkan monad yang memiliki kemampuan logging, tetapi juga melakukan sesuatu yang lain - katakanlah ia juga dapat membaca dari lingkungan. Anda akan menerapkan ini sebagai
type RW r w a = ReaderT r (Writer w a)
Sekarang karena penulis berada di dalam ReaderT
trafo monad, jika Anda ingin mencatat keluaran Anda tidak dapat menggunakan tell w
(karena itu hanya beroperasi dengan penulis yang tidak terbungkus) tetapi Anda harus menggunakan lift $ tell w
, yang "mengangkat" tell
fungsi melalui ReaderT
sehingga dapat mengakses penulis batin monad. Jika Anda menginginkan transformator dua lapisan (katakanlah Anda ingin menambahkan penanganan kesalahan juga) maka Anda perlu menggunakan lift $ lift $ tell w
. Ini dengan cepat menjadi berat.
Sebaliknya, dengan mendefinisikan kelas tipe kita dapat membuat pembungkus trafo monad di sekitar penulis menjadi instance penulis itu sendiri. Sebagai contoh,
instance (Monoid w, MonadWriter w m) => MonadWriter w (ReaderT r m)
yaitu, jika w
monoid, dan m
merupakan a MonadWriter w
, maka ReaderT r m
juga a MonadWriter w
. Artinya kita dapat menggunakan tell
fungsi tersebut secara langsung pada monad yang ditransformasikan, tanpa harus repot-repot mengangkatnya secara eksplisit melalui trafo monad.