learn - ¿Cómo se juega con Control.Monad.Writer en haskell?
monad example (3)
El paquete Control.Monad.Writer
no exporta el Writer
constructor de datos. Supongo que esto fue diferente cuando LYAH fue escrito.
Usando la clase de tipo MonadWriter en ghci
En cambio, crea escritores que usan la función de escritura. 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 algo que implementa la clase de tipo MonadWriter
. Ahora puedo usarlo:
ghci> let multWithLog = do { a <- logNumber 3; b <- logNumber 5; return (a*b) }
:: Writer [String] Int
(Entrada ingresada en realidad en una sola línea). Aquí he especificado el tipo de multWithLog
para ser Writer [String] Int
. Ahora puedo ejecutarlo:
ghci> runWriter multWithLog
(15, ["Got number: 3","Got number: 5"])
Y verá que registramos todas las operaciones intermedias.
¿Por qué el código está escrito así?
¿Por qué molestarse en crear la clase de tipo MonadWriter
? La razón es hacer con los transformadores de mónada. Como se dio cuenta correctamente, la forma más sencilla de implementar Writer
es como un contenedor de tipo nuevo sobre 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 supongamos que quiere una mónada que tenga capacidades de registro, pero también hace otra cosa: decir que también puede leer de un entorno. Implementarías esto como
type RW r w a = ReaderT r (Writer w a)
Ahora, como el escritor está dentro del transformador de mónada ReaderT
, si desea registrar la salida, no puede usar tell w
(porque eso solo funciona con escritores desenvueltos), pero debe usar lift $ tell w
, que "eleva" la función tell
a través del ReaderT
para que pueda acceder a la mónada del escritor interno. Si quería transformadores de dos capas (supongamos que desea agregar el manejo de errores también), entonces necesitaría usar lift $ lift $ tell w
. Esto se vuelve difícil de manejar.
En cambio, al definir una clase de tipo, podemos convertir cualquier envoltura de transformador de mónada alrededor de un escritor en una instancia de escritor. Por ejemplo,
instance (Monoid w, MonadWriter w m) => MonadWriter w (ReaderT r m)
es decir, si w
es un monoide y m
es un MonadWriter w
, entonces ReaderT rm
también es un MonadWriter w
. Esto significa que podemos usar la función tell
directamente en la mónada transformada, sin tener que molestarnos en levantarla explícitamente a través del transformador de mónada.
Soy nuevo en la programación funcional y recientemente aprendí en Learn You a Haskell , pero cuando revisé este capítulo , me quedé atrapado en el siguiente programa:
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)
Guardé estas líneas en un archivo .hs pero no pude importarlo a mi ghci que se quejó:
more1.hs:4:15:
Not in scope: data constructor `Writer''
Perhaps you meant `WriterT'' (imported from Control.Monad.Writer)
Failed, modules loaded: none.
Examiné el tipo con el comando ": info":
Prelude Control.Monad.Writer> :info Writer
type Writer w = WriterT w Data.Functor.Identity.Identity
-- Defined in `Control.Monad.Trans.Writer.Lazy''
Desde mi punto de vista, se suponía que era algo así como "newtype Writer wa ...", así que estoy confundido acerca de cómo alimentar el constructor de datos y obtener un Writer.
Supongo que podría ser un problema relacionado con la versión y mi versión ghci es 7.4.1
Recibí un mensaje similar al probar el LYAH "Por unas pocas Mónadas más" usando el editor en línea de Haskell en repl.it
Cambié la importación de:
import Control.Monad.Writer
a:
import qualified Control.Monad.Trans.Writer.Lazy as W
Entonces mi código ahora funciona así (con inspiración del blog Haskell de Kwang ):
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)
Una función llamada "escritor" está disponible en lugar de un constructor "Escritor". Cambio:
logNumber x = Writer (x, ["Got number: " ++ show x])
a:
logNumber x = writer (x, ["Got number: " ++ show x])