Как играть с Control.Monad.Writer в haskell?

Я новичок в функциональном программировании и недавно изучаю Учите вас в Haskell, но когда я прошел через в этой главе, я застрял в программе ниже:

import Control.Monad.Writer  

logNumber :: Int -> Writer [String] Int  
logNumber x = Writer (x, ["Got number: " ++ show x])  

multWithLog :: Writer [String] Int  
multWithLog = do  
    a <- logNumber 3  
    b <- logNumber 5  
    return (a*b)

Я сохранил эти строки в файле .hs, но не смог импортировать его в свой ghci, который жаловался:

more1.hs:4:15:
    Not in scope: data constructor `Writer'
    Perhaps you meant `WriterT' (imported from Control.Monad.Writer)
Failed, modules loaded: none.

Я исследовал тип командой ": info":

Prelude Control.Monad.Writer> :info Writer
type Writer w = WriterT w Data.Functor.Identity.Identity
               -- Defined in `Control.Monad.Trans.Writer.Lazy'

С моей точки зрения, это должно было быть чем-то вроде "newtype Writer w a..." поэтому я смущен тем, как кормить конструктор данных и получить Writer.

Я предполагаю, что это может быть проблема, связанная с версией, и моя версия ghci - это 7.4.1

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

Ответ 2

Функция, называемая "писатель", становится доступной вместо конструктора "Писатель". Изменить:

logNumber x = Writer (x, ["Got number: " ++ show x])

в

logNumber x = Writer (x, ["Got number: " ++ show x])