Das Paket Control.Monad.Writer
exportiert den Datenkonstruktor nicht Writer
. Ich vermute, dass dies anders war, als LYAH geschrieben wurde.
Verwendung der MonadWriter-Typklasse in ghci
Stattdessen erstellen Sie Writer mit der Option writer
Funktion. In einer ghci-Sitzung kann ich zum Beispiel Folgendes tun
ghci> import Control.Monad.Writer
ghci> let logNumber x = writer (x, ["Got number: " ++ show x])
Jetzt logNumber
ist eine Funktion zur Erstellung von Schriftstellern. Ich kann nach ihrem Typ fragen:
ghci> :t logNumber
logNumber :: (Show a, MonadWriter [String] m) => a -> m a
Was mir sagt, dass der abgeleitete Typ keine Funktion ist, die eine besondere Schreiber, sondern vielmehr alles, was die MonadWriter
Typ Klasse. Ich kann sie jetzt verwenden:
ghci> let multWithLog = do { a <- logNumber 3; b <- logNumber 5; return (a*b) }
:: Writer [String] Int
(Die Eingaben werden in einer Zeile eingegeben). Hier habe ich den Typ von multWithLog
zu sein Writer [String] Int
. Jetzt kann ich es ausführen:
ghci> runWriter multWithLog
(15, ["Got number: 3","Got number: 5"])
Und Sie sehen, dass wir alle Zwischenschritte protokollieren.
Warum ist der Code so geschrieben?
Warum sollte man sich die Mühe machen, die MonadWriter
Typklasse überhaupt? Der Grund liegt in den Monaden-Transformatoren. Wie Sie richtig erkannt haben, ist der einfachste Weg zur Implementierung von Writer
ist als newtype-Wrapper über einem Paar:
newtype Writer w a = Writer { runWriter :: (a,w) }
Sie können dafür eine Monadeninstanz deklarieren und dann die Funktion
tell :: Monoid w => w -> Writer w ()
der einfach seine Eingaben protokolliert. Nehmen wir nun an, Sie wollen eine Monade, die über Logging-Fähigkeiten verfügt, aber auch noch etwas anderes kann - sagen wir, sie kann auch aus einer Umgebung lesen. Sie würden dies implementieren als
type RW r w a = ReaderT r (Writer w a)
Da sich der Autor innerhalb der ReaderT
Monaden-Transformator, wenn Sie die Ausgabe protokollieren wollen, können Sie nicht tell w
(weil das nur mit unverpackten Schreibern funktioniert), aber Sie müssen die lift $ tell w
der die tell
Funktion über die ReaderT
so dass sie auf die innere Schreibmonade zugreifen kann. Wenn Sie zwei Ebenen-Transformatoren wollten (sagen wir, Sie wollten auch Fehlerbehandlung hinzufügen), dann müssten Sie lift $ lift $ tell w
. Das wird schnell unübersichtlich.
Stattdessen können wir durch die Definition einer Typklasse jeden Monaden-Transformer, der einen Writer umgibt, zu einer Instanz von Writer selbst machen. Zum Beispiel,
instance (Monoid w, MonadWriter w m) => MonadWriter w (ReaderT r m)
das heißt, wenn w
ist ein Monoid, und m
ist eine MonadWriter w
dann ReaderT r m
ist auch ein MonadWriter w
. Dies bedeutet, dass wir die tell
Funktion direkt auf die transformierte Monade anwenden, ohne sich die Mühe machen zu müssen, sie explizit durch den Monadentransformator zu heben.