it-swarm.com.ru

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

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

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

81
Javran

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

115
Chris Taylor

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

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

чтобы:

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

4
Marcus

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

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

import Control.Monad.Writer

чтобы:

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

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

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) 

Код в настоящее время выполняется здесь

0
Simon Dowdeswell