wake-up-neo.com

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

Ich bin neu im Bereich der funktionalen Programmierung und lerne gerade bei Learn You a Haskell , aber als ich dieses Kapitel durchgegangen bin, blieb ich beim unten stehenden 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" geprüft:

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 w a ...". Sein. Ich bin also verwirrt, wie man den Datenkonstruktor füttert und einen Writer erhält.

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

81
Javran

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

Verwendung der MonadWriter-Typenklasse in ghci

Stattdessen erstellen Sie Writer mit der Funktion writer. Zum Beispiel in einer Ghci-Sitzung kann ich tun

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

Nun ist logNumber eine Funktion, die Autoren erzeugt. Ich kann nach dem 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 -Schreiber zurückgibt, sondern alles, was die Typklasse MonadWriter 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 von multWithLog als Writer [String] Int angegeben. Jetzt kann ich es ausführen:

ghci> runWriter multWithLog
(15, ["Got number: 3","Got number: 5"])

Und Sie sehen, dass wir alle Zwischenvorgänge protokollieren.

Warum ist der Code so geschrieben?

Warum überhaupt die Typklasse MonadWriter erstellen? Der Grund ist, mit Monadentransformatoren zu tun. Wie Sie richtig verstanden haben, ist der einfachste Weg, Writer zu implementieren, als neuer Wrapper auf einem Paar:

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

Sie können dafü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 noch etwas anderes tut - beispielsweise, dass sie auch aus einer Umgebung lesen kann. Sie würden dies implementieren als

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

Da sich der Writer im ReaderT Monadentransformator befindet, können Sie tell w nicht verwenden, wenn Sie die Ausgabe protokollieren möchten (da dies nur mit nicht umbrochenen Writern funktioniert), sondern müssen lift $ tell w verwenden, der den tell Funktion über ReaderT, damit es auf die Monade des inneren Schriftstellers zugreifen kann. Wenn Sie Transformatoren mit zwei Ebenen benötigen (sagen wir, Sie möchten auch eine Fehlerbehandlung hinzufügen), müssen Sie lift $ lift $ tell w verwenden. Dies wird schnell unhandlich.

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

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

das heißt, wenn w ein Monoid ist und m ein MonadWriter w ist, dann ist ReaderT r m auch ein MonadWriter w. Dies bedeutet, dass wir die Funktion tell direkt auf die transformierte Monade anwenden können, ohne sie explizit durch den Monadentransformator heben zu müssen.

117
Chris Taylor

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])

5
Marcus

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

Ich habe den Import geändert von:

import Control.Monad.Writer

zu:

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

So funktioniert mein Code jetzt so (mit Inspiration aus 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 ist derzeit hier lauffähig

0