Paket Control.Monad.Writertidak mengekspor konstruktor data Writer. Saya rasa ini berbeda ketika LYAH ditulis.
Menggunakan kelas tipe MonadWriter di ghci
Sebaliknya, Anda membuat penulis menggunakan writerfungsi 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 logNumberadalah 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 MonadWriterkelas 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 multWithLogmenjadi 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 MonadWriterkelas tipe? Alasannya ada hubungannya dengan trafo monad. Seperti yang Anda sadari dengan benar, cara termudah untuk mengimplementasikan Writeradalah 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 ReaderTtrafo 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" tellfungsi melalui ReaderTsehingga 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 wmonoid, dan mmerupakan a MonadWriter w, maka ReaderT r mjuga a MonadWriter w. Artinya kita dapat menggunakan tellfungsi tersebut secara langsung pada monad yang ditransformasikan, tanpa harus repot-repot mengangkatnya secara eksplisit melalui trafo monad.