Come si gioca con Control.Monad.Writer in haskell?


96

Sono nuovo nella programmazione funzionale e recentemente ho imparato a Learn You a Haskell , ma quando ho seguito questo capitolo , sono rimasto bloccato con il programma seguente:

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)

Ho salvato queste righe in un file .hs e non sono riuscito a importarlo nel mio ghci che si è lamentato:

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

Ho esaminato il tipo con il comando ": info":

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

Dal mio punto di vista, questo doveva essere qualcosa come "newtype Writer wa ..." quindi sono confuso su come alimentare il costruttore di dati e ottenere un writer.

Immagino che potrebbe essere un problema relativo alla versione e la mia versione di ghci è 7.4.1


2
Per gli esercizi, consiglio di non importare la dichiarazione e di scriverla da soli nel file.
sdcvvc

5
Ho parlato con l'autore qualche tempo fa e lui ha confermato che la versione online del libro non è aggiornata. C'è una versione più aggiornata in PDF: [qui ]
Electric Coffee

@Electric, ho la stessa domanda, potresti fornire un link? Il tuo link sopra è rotto.
Bulat M.

Risposte:


126

Il pacchetto Control.Monad.Writernon esporta il costruttore di dati Writer. Immagino che fosse diverso quando LYAH è stato scritto.

Utilizzo della classe di caratteri MonadWriter in ghci

Invece, crei scrittori usando la writerfunzione. Ad esempio, in una sessione ghci posso fare

ghci> import Control.Monad.Writer
ghci> let logNumber x = writer (x, ["Got number: " ++ show x])

Ora logNumberè una funzione che crea scrittori. Posso chiedere il suo tipo:

ghci> :t logNumber
logNumber :: (Show a, MonadWriter [String] m) => a -> m a

Il che mi dice che il tipo dedotto non è una funzione che restituisce un particolare scrittore, ma piuttosto qualsiasi cosa che implementi la MonadWriterclasse del tipo. Ora posso usarlo:

ghci> let multWithLog = do { a <- logNumber 3; b <- logNumber 5; return (a*b) }
    :: Writer [String] Int

(Input effettivamente immesso tutto su una riga). Qui ho specificato il tipo di multWithLogessere Writer [String] Int. Ora posso eseguirlo:

ghci> runWriter multWithLog
(15, ["Got number: 3","Got number: 5"])

E vedi che registriamo tutte le operazioni intermedie.

Perché il codice è scritto in questo modo?

Perché preoccuparsi di creare la MonadWriterclasse del tipo? Il motivo ha a che fare con i trasformatori monade. Come hai capito correttamente, il modo più semplice per implementare Writerè come un wrapper di nuovo tipo sopra una coppia:

newtype Writer w a = Writer { runWriter :: (a,w) }

Puoi dichiarare un'istanza di monade per questo e quindi scrivere la funzione

tell :: Monoid w => w -> Writer w ()

che registra semplicemente il suo input. Supponiamo ora di volere una monade che abbia capacità di registrazione, ma che faccia anche qualcos'altro - diciamo che può leggere anche da un ambiente. Lo implementeresti come

type RW r w a = ReaderT r (Writer w a)

Ora, poiché il writer è all'interno del ReaderTtrasformatore della monade, se vuoi registrare l'output non puoi usare tell w(perché funziona solo con i writer non imbustati) ma devi usare lift $ tell w, che "solleva" la tellfunzione attraverso il in ReaderTmodo che possa accedere al monade dello scrittore interiore. Se volessi trasformatori a due livelli (diciamo che volevi aggiungere anche la gestione degli errori), allora dovresti usare lift $ lift $ tell w. Questo diventa rapidamente ingombrante.

Invece, definendo una classe di tipo, possiamo trasformare qualsiasi wrapper del trasformatore monade attorno a un writer in un'istanza del writer stesso. Per esempio,

instance (Monoid w, MonadWriter w m) => MonadWriter w (ReaderT r m)

cioè, se wè un monoide ed mè a MonadWriter w, allora ReaderT r mè anche a MonadWriter w. Ciò significa che possiamo utilizzare la tellfunzione direttamente sulla monade trasformata, senza doversi preoccupare di sollevarla esplicitamente attraverso il trasformatore della monade.


31
"Credo che questo fosse diverso quando LYAH è stato scritto." Destra. Ciò è cambiato con il mtlpassaggio dalla versione principale 1. * alla 2. *, poco dopo la scrittura di LYAH e RWH. Tempismo estremamente sfortunato che ha portato e porta a molta confusione tra i principianti.
Daniel Fischer

2
Adesso sto usando GHC versione 7.8.3 e ho dovuto importare Control.Monad.Trans.Writer. Inoltre il tipo di logNumberè logNumber :: (Show a, Monad m) => a -> WriterT [[Char]] m aper me.
kmikael

@kmikael Probabilmente non hai la mtllibreria installata (il che probabilmente significa che hai un'installazione di base di GHC, come minGHC, piuttosto che la piattaforma Haskell). Da un prompt dei comandi run cabal updatee cabal install mtlquindi riprovare.
Chris Taylor

Chris, stavo usando la piattaforma Haskell e mtl è stato installato, l'ho installato di nuovo e ora sembra funzionare come nella tua risposta. Non so cosa fosse successo. Grazie.
kmikael

La copia stampata del libro è invece corretta. Include un paragrafo che spiega che writerviene utilizzato al posto di Writerquest'ultimo, il valore ctor, non viene esportato dal modulo, mentre il primo lo è, e può essere utilizzato per creare lo stesso valore che creeresti con il ctor, ma lo fa non consentire la corrispondenza del modello.
Enrico Maria De Angelis

8

Una funzione chiamata "writer" viene resa disponibile al posto di un costruttore "Writer". Modificare:

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

per:

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


6
Cosa aggiunge questo alle risposte esistenti?
dfeuer

1

Ho ricevuto un messaggio simile provando il LYAH "For a few Monads More" usando l'editor online Haskell in repl.it

Ho cambiato l'importazione da:

import Control.Monad.Writer

per:

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

Quindi il mio codice ora funziona in questo modo (con l'ispirazione dal blog Haskell di 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) 

Il codice è attualmente eseguibile qui

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.