El paquete Control.Monad.Writerno exporta el constructor de datos Writer. Supongo que esto era diferente cuando se escribió LYAH.
Usando la clase de tipo MonadWriter en ghci
En su lugar, crea escritores utilizando la writerfunción. Por ejemplo, en una sesión de ghci puedo hacer
ghci> import Control.Monad.Writer
ghci> let logNumber x = writer (x, ["Got number: " ++ show x])
Ahora logNumberes una función que crea escritores. Puedo preguntar por su tipo:
ghci> :t logNumber
logNumber :: (Show a, MonadWriter [String] m) => a -> m a
Lo que me dice que el tipo inferido no es una función que devuelve un escritor en particular , sino cualquier cosa que implemente la MonadWriterclase de tipo. Ahora puedo usarlo:
ghci> let multWithLog = do { a <- logNumber 3; b <- logNumber 5; return (a*b) }
:: Writer [String] Int
(Entrada realmente ingresada todo en una línea). Aquí he especificado el tipo de multWithLogser Writer [String] Int. Ahora puedo ejecutarlo:
ghci> runWriter multWithLog
(15, ["Got number: 3","Got number: 5"])
Y ves que registramos todas las operaciones intermedias.
¿Por qué el código está escrito así?
¿Por qué molestarse en crear la MonadWriterclase de tipo? La razón tiene que ver con los transformadores de mónadas. Como se dio cuenta correctamente, la forma más sencilla de implementar Writeres como un contenedor de tipo nuevo encima de un par:
newtype Writer w a = Writer { runWriter :: (a,w) }
Puede declarar una instancia de mónada para esto y luego escribir la función
tell :: Monoid w => w -> Writer w ()
que simplemente registra su entrada. Ahora suponga que desea una mónada que tenga capacidades de registro, pero que también haga algo más, digamos que también puede leer desde un entorno. Implementarías esto como
type RW r w a = ReaderT r (Writer w a)
Ahora, debido a que el escritor está dentro del ReaderTtransformador de mónada, si desea registrar la salida, no puede usar tell w(porque eso solo funciona con escritores no envueltos) pero debe usar lift $ tell w, lo que "eleva" la tellfunción a través del ReaderTpara que pueda acceder al mónada del escritor interior. Si desea transformadores de dos capas (digamos que también desea agregar control de errores), entonces debe usarlift $ lift $ tell w . Esto rápidamente se vuelve difícil de manejar.
En cambio, al definir una clase de tipo, podemos convertir cualquier envoltorio transformador de mónada alrededor de un escritor en una instancia del escritor mismo. Por ejemplo,
instance (Monoid w, MonadWriter w m) => MonadWriter w (ReaderT r m)
es decir, si wes un monoide y mes a MonadWriter w, entonces ReaderT r mtambién es a MonadWriter w. Esto significa que podemos usar la tellfunción directamente en la mónada transformada, sin tener que molestarnos en levantarla explícitamente a través del transformador de mónada.