Wie spiele ich mit Control.Monad.Writer in haskell?

96

Ich bin neu in der funktionalen Programmierung und habe kürzlich bei Learn You a Haskell gelernt , aber als ich dieses Kapitel durchging , blieb ich beim folgenden Programm hängen:

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)

Ich habe diese Zeilen in einer .hs-Datei gespeichert und konnte sie nicht in mein ghci importieren, was sich beschwerte:

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

Ich habe den Typ mit dem Befehl ": info" untersucht:

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

Aus meiner Sicht sollte dies so etwas wie "newtype Writer wa ..." sein, also bin ich verwirrt darüber, wie man den Datenkonstruktor füttert und einen Writer bekommt.

Ich denke, es könnte ein Versionsproblem sein und meine ghci-Version ist 7.4.1

Javran
quelle
2
Für Übungen empfehle ich, die Deklaration nicht zu importieren und selbst in die Datei zu schreiben.
SDCVVC
5
Ich habe vor einiger Zeit mit dem Autor gesprochen und er hat bestätigt, dass die Online-Version des Buches veraltet ist. Es gibt eine aktuellere Version als PDF: [hier ]
Electric Coffee
@Electric, ich habe die gleiche Frage, könnten Sie bitte einen Link geben? Ihr obiger Link ist defekt.
Bulat M.
2
@BulatM. Los geht's
Electric Coffee

Antworten:

126

Das Paket Control.Monad.Writerexportiert den Datenkonstruktor nicht Writer. Ich denke, das war anders, als LYAH geschrieben wurde.

Verwenden der MonadWriter-Typklasse in ghci

Stattdessen erstellen Sie Autoren mit der writerFunktion. Zum Beispiel kann ich in einer Ghci-Sitzung tun

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

Jetzt logNumberist eine Funktion, die Schriftsteller erstellt. Ich kann nach seinem 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 einen bestimmten Writer zurückgibt , sondern alles, was die MonadWriterTypklasse implementiert . Ich kann es jetzt benutzen:

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

(Eingabe tatsächlich alle in einer Zeile eingegeben). Hier habe ich den Typ angegeben, der sein multWithLogsoll 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 Zwischenoperationen protokollieren.

Warum ist der Code so geschrieben?

Warum überhaupt die MonadWriterTypklasse erstellen ? Der Grund liegt in Monadentransformatoren. Wie Sie richtig erkannt haben, ist die einfachste Art der Implementierung Writerein Newtype-Wrapper über einem Paar:

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

Sie können hierfür eine Monadeninstanz deklarieren und dann die Funktion schreiben

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

das protokolliert einfach seine Eingabe. Angenommen, Sie möchten eine Monade, die über Protokollierungsfunktionen verfügt, aber auch etwas anderes tut - sagen wir, sie kann auch aus einer Umgebung lesen. Sie würden dies als implementieren

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

Jetzt, da sich der Writer im ReaderTMonadentransformator befindet, können Sie die Ausgabe nicht protokollieren, da Sie sie nur verwenden können tell w(da dies nur bei nicht verpackten Writern funktioniert), sondern müssen sie verwenden lift $ tell w, wodurch die tellFunktion durch die "angehoben" wird, ReaderTdamit sie auf die zugreifen kann innere Schriftsteller Monade. Wenn Sie Transformatoren mit zwei Ebenen möchten (sagen wir, Sie möchten auch die Fehlerbehandlung hinzufügen), müssen Sie diese verwendenlift $ lift $ tell w . Dies wird schnell unhandlich.

Stattdessen können wir durch Definieren einer Typklasse jeden Monadentransformator-Wrapper um einen Writer in eine Instanz des Writers selbst verwandeln. Beispielsweise,

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

das heißt, wenn wes ein Monoid ist und ein mist MonadWriter w, dann ReaderT r mist es auch ein MonadWriter w. Dies bedeutet, dass wir die tellFunktion direkt auf der transformierten Monade verwenden können, ohne uns die Mühe machen zu müssen, sie explizit durch den Monadentransformator anzuheben.

Chris Taylor
quelle
31
"Ich denke, das war anders, als LYAH geschrieben wurde." Richtig. Das änderte sich mit mtldem Wechsel von Hauptversion 1. * zu 2. *, kurz nachdem LYAH und RWH geschrieben wurden. Extrem unglückliches Timing, das bei Anfängern zu viel Verwirrung führte.
Daniel Fischer
2
Ich verwende jetzt GHC Version 7.8.3 und musste importieren Control.Monad.Trans.Writer. Neben der Art logNumberist logNumber :: (Show a, Monad m) => a -> WriterT [[Char]] m afür mich.
kmikael
@kmikael Sie haben wahrscheinlich keine mtlBibliothek installiert (was wahrscheinlich bedeutet, dass Sie eine Basisinstallation von GHC wie minGHC anstelle der Haskell-Plattform haben). Führen Sie an einer Eingabeaufforderung aus cabal updateund cabal install mtlversuchen Sie es erneut.
Chris Taylor
Chris, ich habe die Haskell-Plattform verwendet und das MTL wurde installiert. Ich habe es jedoch erneut installiert und jetzt scheint es wie in Ihrer Antwort zu funktionieren. Ich weiß nicht, was los war. Vielen Dank.
kmikael
Die gedruckte Ausgabe des Buches ist stattdessen korrekt. Es enthält einen Absatz, in dem erläutert wird, dass writeranstelle des Writerletzteren der Wert ctor nicht vom Modul exportiert wird, während der erstere es ist, und er kann verwendet werden, um denselben Wert zu erstellen, den Sie mit dem ctor erstellen würden, dies jedoch tut Mustervergleich nicht zulassen.
Enrico Maria De Angelis
8

Eine Funktion namens "writer" wird anstelle eines "Writer" -Konstruktors zur Verfügung gestellt. Veränderung:

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

zu:

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

Marcus
quelle
6
Was trägt dies zu den vorhandenen Antworten bei?
Feuer
1

Ich habe eine ähnliche Nachricht erhalten, als ich die LYAH "Für ein paar Monaden mehr" mit dem Online-Haskell-Editor in ausprobiert habe repl.it ausprobiert habe

Ich habe den Import geändert von:

import Control.Monad.Writer

zu:

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

Mein Code sieht jetzt so aus (mit Inspiration von Kwangs Haskell-Blog ):

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) 

Code kann derzeit hier ausgeführt werden

Simon Dowdeswell
quelle