Як грати з 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. Ось
Electric Coffee

Відповіді:


127

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


31
"Я думаю, це було інакше, коли писали LYAH". Правильно. Це змінилося з mtlпереходом від основної версії 1. * до 2. *, незабаром після написання LYAH та RWH. Надзвичайно невдалий час, який призвів до великої плутанини серед початківців.
Даніель Фішер,

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 "На кілька монад більше" за допомогою онлайн-редактора Haskell у repl.it

Я змінив імпорт із:

import Control.Monad.Writer

до:

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

Тож мій код зараз працює таким чином (натхненно з блогу Кванга 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) 

Наразі код можна використовувати тут

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.