Я новичок в функциональном программировании и недавно учился на 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
Ответы:
Пакет
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
функцию непосредственно на преобразованной монаде, не беспокоясь о ее явном поднятии через преобразователь монады.источник
mtl
переходом от основной версии 1. * к 2. *, вскоре после того, как были написаны LYAH и RWH. Крайне неудачный выбор времени, который привел к большому замешательству среди новичков.Control.Monad.Trans.Writer
. Вдобавок типlogNumber
подходитlogNumber :: (Show a, Monad m) => a -> WriterT [[Char]] m a
мне.mtl
библиотека (что, вероятно, означает, что у вас есть базовая установка GHC, например minGHC, а не платформа Haskell). Из командной строки запуститеcabal update
иcabal install mtl
попробуйте еще раз.writer
используется вместоWriter
последнего, значение ctor, не экспортируется модулем, в то время как первое экспортируется, и его можно использовать для создания того же значения, которое вы создали бы с помощью ctor, но делает не разрешать сопоставление с образцом.Функция с именем «писатель» предоставляется вместо конструктора «писатель». Изменить:
logNumber x = Writer (x, ["Got number: " ++ show x])
кому:
logNumber x = writer (x, ["Got number: " ++ show x])
источник
Я получил подобное сообщение, попробовав 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)
Код в настоящее время можно запустить здесь
источник