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

97

Я новичок в функциональном программировании и недавно учился на Learn You a 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 wa ...", так что я не понимаю, как передать конструктору данных и получить Writer.

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

Джавран
источник
2
Для упражнений рекомендую не импортировать декларацию, а самостоятельно записать ее в файл.
sdcvvc
5
Некоторое время назад я разговаривал с автором, и он подтвердил, что онлайн-версия книги устарела. В формате PDF есть более свежая версия: [здесь ]
Electric Coffee
@ Electric, у меня такой же вопрос, не могли бы вы дать ссылку? Ваша ссылка выше не работает.
Булат М.
2
@BulatM. Здесь вы идете
кофемашина

Ответы:

127

Пакет 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функцию непосредственно на преобразованной монаде, не беспокоясь о ее явном поднятии через преобразователь монады.

Крис Тейлор
источник
31
«Я думаю, это было иначе, когда был написан ЛЯ». Правильно. Это изменилось с mtlпереходом от основной версии 1. * к 2. *, вскоре после того, как были написаны LYAH и RWH. Крайне неудачный выбор времени, который привел к большому замешательству среди новичков.
Daniel Fischer
2
Сейчас я использую GHC версии 7.8.3, и мне пришлось импортировать Control.Monad.Trans.Writer. Вдобавок тип logNumberподходит logNumber :: (Show a, Monad m) => a -> WriterT [[Char]] m aмне.
kmikael
@kmikael У вас, вероятно, не установлена mtlбиблиотека (что, вероятно, означает, что у вас есть базовая установка GHC, например minGHC, а не платформа Haskell). Из командной строки запустите cabal updateи cabal install mtlпопробуйте еще раз.
Крис Тейлор
Крис, я использовал платформу Haskell, и mtl был установлен, но я установил его снова, и теперь, похоже, он работает, как в вашем ответе. Я не знаю, что случилось. Спасибо.
kmikael
Напечатанная копия книги правильная. Он включает в себя абзац, объясняющий, что writerиспользуется вместо Writerпоследнего, значение ctor, не экспортируется модулем, в то время как первое экспортируется, и его можно использовать для создания того же значения, которое вы создали бы с помощью ctor, но делает не разрешать сопоставление с образцом.
Энрико
8

Функция с именем «писатель» предоставляется вместо конструктора «писатель». Изменить:

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

кому:

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

Маркус
источник
6
Что это добавляет к существующим ответам?
dfeuer
1

Я получил подобное сообщение, попробовав LYAH "For a few Monads More" с помощью онлайн-редактора Haskell в repl.it

Я изменил импорт с:

import Control.Monad.Writer

кому:

import qualified Control.Monad.Trans.Writer.Lazy as W

Итак, мой код теперь выглядит следующим образом (вдохновленный блогом Kwang Haskell ):

import Data.Monoid
import qualified Control.Monad.Trans.Writer.Lazy as W


output :: String -> W.Writer [String] ()
output x = W.tell [x]


gcd' :: Int -> Int -> W.Writer [String] Int  
gcd' a b  
    | b == 0 = do  
        output ("Finished with " ++ show a)
        return a  
    | otherwise = do  
        output (show a ++ " mod " ++ show b ++ " = " ++ show (a `mod` b))
        gcd' b (a `mod` b)

main :: IO()
main = mapM_ putStrLn $ snd $ W.runWriter (gcd' 8 3) 

Код в настоящее время можно запустить здесь

Саймон Доудесвелл
источник