Il pacchetto Control.Monad.Writer
non 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 writer
funzione. 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 MonadWriter
classe 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 multWithLog
essere 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 MonadWriter
classe 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 ReaderT
trasformatore 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 tell
funzione attraverso il in ReaderT
modo 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 tell
funzione direttamente sulla monade trasformata, senza doversi preoccupare di sollevarla esplicitamente attraverso il trasformatore della monade.