O pacote Control.Monad.Writer
nã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 writer
funçã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 MonadWriter
classe 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 multWithLog
ser 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 MonadWriter
classe 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 ReaderT
transformador 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 tell
funçã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 w
for um monóide e m
for a MonadWriter w
, então ReaderT r m
também será a MonadWriter w
. Isso significa que podemos usar a tell
função diretamente na mônada transformada, sem ter que nos preocupar em levantá-la explicitamente através do transformador da mônada.