Le paquet Control.Monad.Writer
ne pas exporter les données constructeur Writer
. Je suppose que c'était différent quand LYAH a été écrit.
À l'aide de la MonadWriter typeclass dans ghci
Au lieu de cela, vous créez des écrivains à l'aide de l' writer
fonction. Par exemple, dans un ghci session je peux faire
ghci> import Control.Monad.Writer
ghci> let logNumber x = writer (x, ["Got number: " ++ show x])
Maintenant, logNumber
est une fonction qui crée des écrivains. Je peux demander pour son type:
ghci> :t logNumber
logNumber :: (Show a, MonadWriter [String] m) => a -> m a
Qui me dit que le type inféré n'est pas une fonction qui renvoie un particulier de l'auteur, mais plutôt quelque chose qui implémente l' MonadWriter
type de classe. Je peux maintenant utiliser:
ghci> let multWithLog = do { a <- logNumber 3; b <- logNumber 5; return (a*b) }
:: Writer [String] Int
(Entrée fait saisie 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 connecter tous les intermédiaires en opérations.
Pourquoi le code écrit comme cela?
Pourquoi s'embêter à créer l' MonadWriter
type de classe? La raison en est à faire avec la monade des transformateurs. Comme vous l'avez correctement réalisé, la façon la plus simple à mettre en oeuvre Writer
est un newtype wrapper sur le dessus de la paire:
newtype Writer w a = Writer { runWriter :: (a,w) }
Vous pouvez déclarer une instance de monad pour cela, et puis écrire la fonction
tell :: Monoid w => w -> Writer w ()
qui se connecte simplement à son entrée. Maintenant, supposons que vous voulez une monade qui a des fonctionnalités de journalisation, mais aussi fait quelque chose d'autre - dire qu'il peut lire d'un environnement trop. Vous mettriez ce que
type RW r w a = ReaderT r (Writer w a)
Maintenant, parce que l'écrivain est à l'intérieur de l' ReaderT
monade transformateur, si vous souhaitez vous connecter la sortie, vous ne pouvez pas utiliser tell w
(parce que cela ne fonctionne qu'avec déballé écrivains), mais vous devez utiliser lift $ tell w
, ce qui "soulève" l' tell
de la fonction par le biais de l' ReaderT
, de sorte qu'il peut accéder à l'intérieur de l'écrivain monade. Si vous voulait deux couches de transformateurs (supposons que vous vouliez ajouter la gestion des erreurs en tant que bien), alors vous devez utiliser lift $ lift $ tell w
. Cela devient vite lourd.
Au lieu de cela, par la définition d'un type de classe, nous pouvons faire toute monade transformateur wrapper autour d'un écrivain dans une instance de l'écrivain lui-même. Par exemple,
instance (Monoid w, MonadWriter w m) => MonadWriter w (ReaderT r m)
c'est, si w
est un monoïde, et m
est MonadWriter w
, alors ReaderT r m
est aussi un MonadWriter w
. Cela signifie que nous pouvons utiliser l' tell
fonction directement sur le transformé monade, sans avoir à se préoccuper explicitement le soulevant par le biais de la monade transformateur.