O pacote Control.Monad.Writernão exporta o construtor de dados Writer. Eu acho que isso era diferente quando LYAH foi escrito.
Usando a typeclass MonadWriter em ghci
Em vez disso, você cria escritores usando a writerfunção. Por exemplo, em uma sessão ghci eu posso fazer
ghci> import Control.Monad.Writer
ghci> let logNumber x = writer (x, ["Got number: " ++ show x])
Agora logNumberé uma função que cria escritores. Posso pedir seu tipo:
ghci> :t logNumber
logNumber :: (Show a, MonadWriter [String] m) => a -> m a
O que me diz que o tipo inferido não é uma função que retorna um escritor específico , mas sim qualquer coisa que implemente a MonadWriterclasse de tipo. Agora posso usar:
ghci> let multWithLog = do { a <- logNumber 3; b <- logNumber 5; return (a*b) }
:: Writer [String] Int
(Na verdade, a entrada foi inserida em uma linha). Aqui, especifiquei o tipo de multWithLogser Writer [String] Int. Agora posso executá-lo:
ghci> runWriter multWithLog
(15, ["Got number: 3","Got number: 5"])
E você vê que registramos todas as operações intermediárias.
Por que o código é escrito assim?
Por que se preocupar em criar a MonadWriterclasse de tipo? A razão tem a ver com transformadores de mônadas. Como você percebeu corretamente, a maneira mais simples de implementar Writeré como um wrapper de novo tipo em cima de um par:
newtype Writer w a = Writer { runWriter :: (a,w) }
Você pode declarar uma instância de mônada para isso e, em seguida, escrever a função
tell :: Monoid w => w -> Writer w ()
que simplesmente registra sua entrada. Agora, suponha que você queira uma mônada que tenha recursos de registro, mas também faça outra coisa - digamos que ela possa ler de um ambiente também. Você implementaria isso como
type RW r w a = ReaderT r (Writer w a)
Agora, como o gravador está dentro do ReaderTtransformador de mônada, se você deseja registrar a saída, não pode usar tell w(porque isso só funciona com gravadores não embalados), mas tem que usar lift $ tell w, o que "levanta" a tellfunção através doReaderT para que possa acessar o mônada interior do escritor. Se você quiser dois transformadores de camadas (digamos que você também queira adicionar tratamento de erros), será necessário usá-los lift $ lift $ tell w. Isso rapidamente se torna difícil.
Em vez disso, ao definir uma classe de tipo, podemos transformar qualquer transformador de mônada que envolve um escritor em uma instância do próprio escritor. Por exemplo,
instance (Monoid w, MonadWriter w m) => MonadWriter w (ReaderT r m)
ou seja, se wfor um monóide e mfor a MonadWriter w, então ReaderT r mtambém será a MonadWriter w. Isso significa que podemos usar a tellfunção diretamente na mônada transformada, sem ter que nos preocupar em levantá-la explicitamente através do transformador da mônada.