Suche…


Ein monadischer Zähler

Ein Beispiel, wie Leser, Schreiber und Zustandsmonade mithilfe von Monadetransformatoren erstellt werden. Der Quellcode befindet sich in diesem Repository

Wir möchten einen Zähler implementieren, der seinen Wert um eine bestimmte Konstante erhöht.

Wir beginnen mit der Definition einiger Typen und Funktionen:

newtype Counter = MkCounter {cValue :: Int}
  deriving (Show)

-- | 'inc c n' increments the counter by 'n' units.
inc :: Counter -> Int -> Counter
inc (MkCounter c) n = MkCounter (c + n)

Angenommen, wir möchten die folgende Berechnung mit dem Zähler ausführen:

  • Setzen Sie den Zähler auf 0
  • Setze die Inkrementkonstante auf 3
  • Erhöhen Sie den Zähler dreimal
  • Setze die Inkrementkonstante auf 5
  • Erhöhen Sie den Zähler zweimal

Die Zustandsmonade stellt Abstraktionen für die Weitergabe von Zuständen bereit. Wir können die Zustandsmonade nutzen und unsere Zuwachsfunktion als Zustandstransformator definieren.

-- | CounterS is a monad.
type CounterS = State Counter

-- | Increment the counter by 'n' units.
incS :: Int-> CounterS ()
incS n = modify (\c -> inc c n)

Dies ermöglicht es uns bereits, eine Berechnung klarer und prägnanter auszudrücken:

-- | The computation we want to run, with the state monad.
mComputationS :: CounterS ()
mComputationS = do
  incS 3
  incS 3
  incS 3
  incS 5
  incS 5

Die Inkrementkonstante muss jedoch bei jedem Aufruf weitergegeben werden. Das möchten wir gerne vermeiden.

Umgebung hinzufügen

Die Leser-Monade bietet eine bequeme Möglichkeit, eine Umgebung zu umgehen. Diese Monade wird in der Funktionsprogrammierung verwendet, um in der OO-Welt sogenannte Abhängigkeitsinjektion durchzuführen.

In der einfachsten Version erfordert die Leser-Monade zwei Arten:

  • der Typ des Wertes gelesen werden (dh unsere Umwelt, r unten),

  • der von der Lesermonade zurückgegebene Wert ( a unten).

    Leser ra

Allerdings müssen wir auch die Staatsmonade nutzen. Daher müssen wir den ReaderT Transformator verwenden:

newtype ReaderT r m a :: * -> (* -> *) -> * -> *

Mit ReaderT können wir unseren Zähler mit Umgebung und Status wie folgt definieren:

type CounterRS = ReaderT Int CounterS

Wir definieren eine incR Funktion, die die Inkrement-Konstante aus der Umgebung (mithilfe von ask ) entnimmt, und um unsere Inkrement-Funktion in Bezug auf unsere CounterS Monade zu definieren, verwenden wir die lift Funktion (die zur Monad-Transformator- Klasse gehört).

-- | Increment the counter by the amount of units specified by the environment.
incR :: CounterRS ()
incR = ask >>= lift . incS

Mit der Lesermonade können wir unsere Berechnung folgendermaßen definieren:

-- | The computation we want to run, using reader and state monads.
mComputationRS :: CounterRS ()
mComputationRS = do
  local (const 3) $ do
    incR
    incR
    incR
    local (const 5) $ do
      incR
      incR

Die Anforderungen haben sich geändert: Wir brauchen Protokollierung!

Nehmen wir nun an, dass wir unsere Berechnungen um Protokollierung erweitern möchten, damit wir die Entwicklung unseres Zählers rechtzeitig sehen können.

Wir haben auch eine Monade, um diese Aufgabe auszuführen, die Schriftsteller-Monade . Wie bei der Reader-Monade müssen wir, da wir sie komponieren, den Reader-Monad-Transformator verwenden:

newtype WriterT w m a :: * -> (* -> *) -> * -> *

Hier w die Art der Ausgabe zu akkumulieren darstellt (die eine monoid sein muss, die es uns ermöglichen , diesen Wert zu akkumulieren), m ist der innere monadisch, und a die Art der Berechnung.

Wir können dann unseren Zähler mit Protokollierung, Umgebung und Status wie folgt definieren:

type CounterWRS = WriterT [Int] CounterRS

Mithilfe von lift können Sie die Version der Inkrementierungsfunktion definieren, die den Wert des Zählers nach jedem Inkrement protokolliert:

incW :: CounterWRS ()
incW = lift incR >> get >>= tell . (:[]) . cValue

Nun kann die Berechnung, die die Protokollierung enthält, wie folgt geschrieben werden:

mComputationWRS :: CounterWRS ()
mComputationWRS = do
  local (const 3) $ do
    incW
    incW
    incW
    local (const 5) $ do
      incW
      incW

Alles auf einmal machen

Dieses Beispiel soll Monadentransformatoren bei der Arbeit zeigen. Der gleiche Effekt kann jedoch erzielt werden, wenn alle Aspekte (Umgebung, Status und Protokollierung) in einem einzigen Inkrementierungsvorgang zusammengestellt werden.

Dazu verwenden wir Typeinschränkungen:

inc' :: (MonadReader Int m, MonadState Counter m, MonadWriter [Int] m) => m ()
inc' = ask >>= modify . (flip inc) >> get >>= tell . (:[]) . cValue

Hier kommen wir zu einer Lösung, die für jede Monade funktioniert, die die oben genannten Einschränkungen erfüllt. Die Berechnungsfunktion ist also mit type definiert:

mComputation' :: (MonadReader Int m, MonadState Counter m, MonadWriter [Int] m) => m ()

da wir in ihrem Körper inc verwenden.

Wir können diese Berechnung beispielsweise in der ghci REPL wie folgt ghci :

runState ( runReaderT ( runWriterT mComputation' ) 15 )  (MkCounter 0)


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow