Пакет Control.Monad.Writerне експортує конструктор даних Writer. Я думаю, це було інакше, коли писали LYAH.
Використання класу типів MonadWriter у ghci
Натомість ви створюєте автори за допомогою writerфункції. Наприклад, на сеансі ghci я можу це зробити
ghci> import Control.Monad.Writer
ghci> let logNumber x = writer (x, ["Got number: " ++ show x])
Тепер logNumberце функція, яка створює авторів. Я можу запитати його тип:
ghci> :t logNumber
logNumber :: (Show a, MonadWriter [String] m) => a -> m a
Що говорить мені, що виведений тип - це не функція, яка повертає певного автора, а все, що реалізує MonadWriterклас типу. Тепер я можу ним користуватися:
ghci> let multWithLog = do { a <- logNumber 3; b <- logNumber 5; return (a*b) }
:: Writer [String] Int
(Введення фактично вводиться все в одному рядку). Тут я вказав тип multWithLogбути Writer [String] Int. Тепер я можу запустити його:
ghci> runWriter multWithLog
(15, ["Got number: 3","Got number: 5"])
І ви бачите, що ми реєструємо всі проміжні операції.
Чому код пишеться так?
Навіщо взагалі турбуватися про створення MonadWriterкласу типу? Причиною є монадні трансформатори. Як ви правильно зрозуміли, найпростіший спосіб реалізації Writer- це обгортка нового типу поверх пари:
newtype Writer w a = Writer { runWriter :: (a,w) }
Ви можете оголосити для цього екземпляр монади, а потім написати функцію
tell :: Monoid w => w -> Writer w ()
який просто реєструє свої дані. Тепер припустимо, ви хочете монаду, яка має можливості реєстрації, але також робить щось інше - скажімо, вона також може читати з середовища. Ви б реалізували це як
type RW r w a = ReaderT r (Writer w a)
Тепер, оскільки запис знаходиться всередині ReaderTмонадного трансформатора, якщо ви хочете реєструвати вихідні дані, які ви не можете використовувати tell w(оскільки це працює лише з нерозгорнутими записами), але ви повинні використовувати lift $ tell w, що "піднімає" tellфункцію через, ReaderTщоб вона могла отримати доступ до внутрішня письменницька монада. Якщо вам потрібні двошарові трансформатори (скажімо, ви також хочете додати обробку помилок), вам доведеться використовувати lift $ lift $ tell w. Це швидко стає громіздким.
Натомість, визначивши клас типу, ми можемо зробити будь-яку обмотку трансформатора монади навколо записувача в екземпляр самого записувача. Наприклад,
instance (Monoid w, MonadWriter w m) => MonadWriter w (ReaderT r m)
тобто якщо wє моноїдом і mє a MonadWriter w, то ReaderT r mтакож є a MonadWriter w. Це означає, що ми можемо використовувати tellфункцію безпосередньо на трансформованій монаді, не мучачись явним підйомом її через монадний трансформатор.