El paquete Control.Monad.Writer
no 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 writer
funció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 logNumber
es 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 MonadWriter
clase 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 multWithLog
ser 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 MonadWriter
clase 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 Writer
es 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 ReaderT
transformador 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 tell
función a través del ReaderT
para 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 w
es un monoide y m
es a MonadWriter w
, entonces ReaderT r m
también es a MonadWriter w
. Esto significa que podemos usar la tell
función directamente en la mónada transformada, sin tener que molestarnos en levantarla explícitamente a través del transformador de mónada.