¿Cómo jugar con Control.Monad.Writer en haskell?

97

Soy nuevo en la programación funcional y recientemente aprendí en Learn You a Haskell , pero cuando revisé este capítulo , me quedé atascado con 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 y 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 por 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 esto 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 de ghci es 7.4.1

Javran
fuente
2
Para los ejercicios, recomiendo no importar la declaración y escribirla usted mismo en el archivo.
sdcvvc
5
Hablé con el autor hace un tiempo y me confirmó que la versión en línea del libro no está actualizada. Hay una versión más actualizada en PDF: [aquí ]
Café eléctrico
@Electric, tengo la misma pregunta, ¿podría dar un enlace? Tu enlace anterior está roto.
Bulat M.
2
@BulatM. Aquí tienes
Café eléctrico

Respuestas:

127

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.

Chris Taylor
fuente
31
"Supongo que esto era diferente cuando se escribió LYAH". Correcto. Eso cambió al mtlpasar de la versión principal 1. * a 2. *, poco después de que se escribieran LYAH y RWH. Momento extremadamente desafortunado que llevó y conduce a mucha confusión entre los principiantes.
Daniel Fischer
2
Estoy usando GHC versión 7.8.3 ahora y tuve que importar Control.Monad.Trans.Writer. Además el tipo de logNumberes logNumber :: (Show a, Monad m) => a -> WriterT [[Char]] m apara mí.
kmikael
@kmikael Probablemente no tenga la mtlbiblioteca instalada (lo que probablemente signifique que tiene una instalación básica de GHC, como minGHC, en lugar de la plataforma Haskell). Desde el símbolo del sistema, ejecute cabal updatey cabal install mtlvuelva a intentarlo.
Chris Taylor
Chris, estaba usando la plataforma Haskell y se instaló mtl, sin embargo, lo instalé nuevamente y ahora parece funcionar como en su respuesta. No sé qué estaba mal. Gracias.
kmikael
En cambio, la copia impresa del libro es correcta. Incluye un párrafo que explica que writerse usa en lugar de Writercomo el último, el valor ctor, no es exportado por el módulo, mientras que el primero sí, y se puede usar para crear el mismo valor que crearía con el ctor, pero no no permitir la coincidencia de patrones.
Enrico Maria De Angelis
8

Una función llamada "escritor" está disponible en lugar de un constructor "Writer". Cambio:

logNumber x = Writer (x, ["Got number: " ++ show x])

a:

logNumber x = writer (x, ["Got number: " ++ show x])

Marcus
fuente
6
¿Qué agrega esto a las respuestas existentes?
partir
1

Recibí un mensaje similar al probar LYAH "For a few Monads More" usando el editor Haskell en línea 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 con este aspecto (inspirado en el 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) 

El código se puede ejecutar actualmente aquí

Simon Dowdeswell
fuente