Le package Control.Monad.Writer
n'exporte pas le constructeur de données Writer
. Je suppose que c'était différent lorsque LYAH a été écrit.
Utilisation de la classe de types MonadWriter dans ghci
Au lieu de cela, vous créez des rédacteurs à l'aide de la writer
fonction. Par exemple, dans une session ghci, je peux faire
ghci> import Control.Monad.Writer
ghci> let logNumber x = writer (x, ["Got number: " ++ show x])
C'est maintenant logNumber
une fonction qui crée des écrivains. Je peux demander son type:
ghci> :t logNumber
logNumber :: (Show a, MonadWriter [String] m) => a -> m a
Ce qui me dit que le type inféré n'est pas une fonction qui renvoie un écrivain particulier , mais plutôt tout ce qui implémente la MonadWriter
classe de type. Je peux maintenant l'utiliser:
ghci> let multWithLog = do { a <- logNumber 3; b <- logNumber 5; return (a*b) }
:: Writer [String] Int
(Entrée en fait entré tout sur une seule ligne). Ici, j'ai spécifié le type d' multWithLog
être Writer [String] Int
. Maintenant je peux l'exécuter:
ghci> runWriter multWithLog
(15, ["Got number: 3","Got number: 5"])
Et vous voyez que nous enregistrons toutes les opérations intermédiaires.
Pourquoi le code est-il écrit comme ça?
Pourquoi se donner la peine de créer la MonadWriter
classe de type? La raison est à voir avec les transformateurs monades. Comme vous l'avez bien compris, le moyen le plus simple d'implémenter Writer
est de créer un wrapper de nouveau type au-dessus d'une paire:
newtype Writer w a = Writer { runWriter :: (a,w) }
Vous pouvez déclarer une instance de monade pour cela, puis écrire la fonction
tell :: Monoid w => w -> Writer w ()
qui enregistre simplement son entrée. Supposons maintenant que vous vouliez une monade qui ait des capacités de journalisation, mais qui fasse également autre chose - disons qu'elle peut également lire à partir d'un environnement. Vous implémenteriez cela comme
type RW r w a = ReaderT r (Writer w a)
Maintenant, parce que l'écrivain est à l'intérieur du ReaderT
transformateur monade, si vous voulez enregistrer la sortie, vous ne pouvez pas utiliser tell w
(car cela ne fonctionne qu'avec des écrivains non emballés) mais vous devez utiliser lift $ tell w
, ce qui "soulève" la tell
fonction à travers le ReaderT
afin qu'elle puisse accéder au monade d'écrivain intérieur. Si vous vouliez des transformateurs à deux couches (disons que vous vouliez également ajouter la gestion des erreurs), vous devez les utiliser lift $ lift $ tell w
. Cela devient rapidement difficile à manier.
Au lieu de cela, en définissant une classe de type, nous pouvons transformer n'importe quel wrapper de transformateur monade autour d'un écrivain en une instance de writer lui-même. Par exemple,
instance (Monoid w, MonadWriter w m) => MonadWriter w (ReaderT r m)
c'est-à-dire que si w
est un monoïde, et m
est a MonadWriter w
, alors ReaderT r m
est également a MonadWriter w
. Cela signifie que nous pouvons utiliser la tell
fonction directement sur la monade transformée, sans avoir à se soucier de la soulever explicitement à travers le transformateur de monade.