105 Stimmen

Wie spielt man mit Control.Monad.Writer in Haskell?

Ich bin neu in der funktionalen Programmierung und lerne seit kurzem bei Lernen Sie ein Haskell aber als ich durchging dieses Kapitel Ich bin bei dem unten stehenden Programm hängen geblieben:

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, konnte sie aber nicht in meinen ghci importieren, der 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'

Meiner Meinung nach sollte das so etwas sein wie "newtype Writer w a ..." Ich bin also verwirrt, wie man den Datenkonstruktor füttert und einen Writer erhält.

Ich vermute, dass es sich um ein versionsbezogenes Problem handelt, und meine ghci-Version ist 7.4.1

136voto

Chris Taylor Punkte 45804

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.

9voto

Marcus Punkte 111

Eine Funktion namens "writer" wird anstelle eines "Writer"-Konstruktors zur Verfügung gestellt. Ändern:

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

zu:

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

1voto

Simon Dowdeswell Punkte 893

Ich erhielt eine ähnliche Meldung, als ich die LYAH "Für ein paar Monaden mehr" unter Verwendung des Online-Haskell-Editors in repl.it

Ich habe den Import von geändert:

import Control.Monad.Writer

zu:

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

Mein Code sieht also jetzt so aus (mit Inspiration durch 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) 

Der Code ist derzeit hier lauffähig

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X