Ответ 1
Пакет 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 поверх пары:
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
непосредственно на преобразованной монаде, не требуя явного подъема ее через трансформатор монады.