Пакет Control.Monad.Writer
не экспортирует конструктор данных Writer
. Думаю, когда писали ЛЯХ, все было по-другому.
Использование класса типов 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 поверх пары:
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
является а MonadWriter w
, то ReaderT r m
также является а MonadWriter w
. Это означает, что мы можем использовать tell
функцию непосредственно на преобразованной монаде, не беспокоясь о ее явном поднятии через преобразователь монады.