Como jogar com Control.Monad.Writer em haskell?

96

Eu sou novo em programação funcional e recentemente aprendi em Learn You a Haskell , mas quando li este capítulo , fiquei preso no programa abaixo:

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)

Salvei essas linhas em um arquivo .hs, mas não consegui importá-lo para o meu ghci que reclamava:

more1.hs:4:15:
    Not in scope: data constructor `Writer'
    Perhaps you meant `WriterT' (imported from Control.Monad.Writer)
Failed, modules loaded: none.

Eu examinei o tipo pelo comando ": info":

Prelude Control.Monad.Writer> :info Writer
type Writer w = WriterT w Data.Functor.Identity.Identity
               -- Defined in `Control.Monad.Trans.Writer.Lazy'

Do meu ponto de vista, isso deveria ser algo como "newtype Writer wa ...", então estou confuso sobre como alimentar o construtor de dados e obter um Writer.

Acho que pode ser um problema relacionado à versão e minha versão ghci é 7.4.1

Javran
fonte
2
Para exercícios, eu recomendo não importar a declaração e escrevê-la sozinho no arquivo.
sdcvvc
5
Falei com o autor há algum tempo e ele confirmou que a versão online do livro está desatualizada. Há uma versão mais atualizada em PDF: [aqui ]
Electric Coffee
@Electric, eu tenho a mesma pergunta, você poderia dar um link? Seu link acima está quebrado.
Bulat M.
2
@BulatM. Aqui está
Electric Coffee

Respostas:

126

O pacote Control.Monad.Writernã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 writerfunçã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 MonadWriterclasse 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 multWithLogser 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 MonadWriterclasse 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 ReaderTtransformador 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 tellfunçã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 wfor um monóide e mfor a MonadWriter w, então ReaderT r mtambém será a MonadWriter w. Isso significa que podemos usar a tellfunção diretamente na mônada transformada, sem ter que nos preocupar em levantá-la explicitamente através do transformador da mônada.

Chris Taylor
fonte
31
"Eu acho que isso era diferente quando LYAH foi escrito." Certo. Isso mudou com a mtlpassagem da versão principal 1. * para a 2. *, logo depois que LYAH e RWH foram escritos. Momento extremamente infeliz que levou e leva a muita confusão entre os iniciantes.
Daniel Fischer
2
Estou usando o GHC versão 7.8.3 agora e tive que importar Control.Monad.Trans.Writer. Além disso, o tipo de logNumberé logNumber :: (Show a, Monad m) => a -> WriterT [[Char]] m apara mim.
kmikael
@kmikael Você provavelmente não tem a mtlbiblioteca instalada (o que provavelmente significa que você tem uma instalação básica do GHC, como minGHC, ao invés da Plataforma Haskell). Em um prompt de comando, execute cabal updatee cabal install mtltente novamente.
Chris Taylor
Chris, eu estava usando a plataforma Haskell e o mtl foi instalado, porém instalei novamente e agora parece funcionar como na sua resposta. Eu não sei o que estava errado. Obrigado.
kmikael
A cópia impressa do livro está correta. Inclui um parágrafo explicando que writeré usado no lugar do Writerúltimo, o ctor de valor, não é exportado pelo módulo, enquanto o primeiro é, e pode ser usado para criar o mesmo valor que você criaria com o ctor, mas o faz não permitir correspondência de padrões.
Enrico Maria De Angelis
8

Uma função chamada "escritor" é disponibilizada no lugar de um construtor "Escritor". Mudança:

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

para:

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

Marcus
fonte
6
O que isso adiciona às respostas existentes?
dfeuer
1

Recebi uma mensagem semelhante ao tentar o LYAH "For a few Monads More" usando o editor Haskell online em repl.it

Eu mudei a importação de:

import Control.Monad.Writer

para:

import qualified Control.Monad.Trans.Writer.Lazy as W

Então, meu código agora funciona assim (com inspiração no 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) 

O código pode ser executado aqui

Simon Dowdeswell
fonte