Monadentransformer — Bausteine, die eine Monade um die Fähigkeiten einer anderen erweitern, ohne beide von Hand zu verschmelzen. Sie lösen das Problem, dass eine einzelne Monade nur einen Kontext trägt: Mit Transformatoren werden zum Beispiel Fehlerbehandlung, Zustand und Ein-/Ausgabe in einem einzigen Typ kombiniert.

Das Problem: nur ein Kontext pro Monade

Die Maybe-Monade kann Werte, die fehlen können. Die IO-Monade kann Seiteneffekte. Aber was, wenn eine Datei gelesen werden soll, die möglicherweise nicht existiert? Beide Eigenschaften sind nötig — doch Maybe IO a und IO (Maybe a) verhalten sich unterschiedlich und sind unpraktisch zu schreiben.

Ein Monadentransformer ist ein Wrapper-Typ, der eine innere Monade um eine zusätzliche Fähigkeit erweitert. MaybeT IO a ist ein Transformer, der die IO-Monade um Maybe-Fähigkeiten erweitert: Die Berechnung läuft in IO, kann aber jederzeit mit Nothing abbrechen.

newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }

Die Standardsammlung

Fast jede gängige Monade hat eine Transformer-Variante:

  • MaybeT — Optionalität, EitherT (bzw. ExceptT) — Fehler als Werte
  • StateT — Zustand (State-Monade)
  • ReaderT — schreibgeschützte Umgebung (Reader-Monade)
  • WriterT — akkumulierte Ausgabe (Writer-Monade)
  • ContT — Fortsetzungen

Ein Transformer nimmt die innere Monade als Parameter: StateT s (ReaderT r IO) a ist ein Stack aus IO, Umgebung und Zustand. Die Stapelreihenfolge entscheidet über die Semantik — StateT s Maybe und MaybeT (State s) verhalten sich bei Fehlern unterschiedlich, weil ein abgebrochener Zustand entweder verloren geht oder erhalten bleibt.

Lift und liftIO: durch den Stack reisen

Die Funktion lift :: m a -> t m a (Klasse MonadTrans) hebt eine Aktion der inneren Monade um eine Ebene in den Stack. Für IO gibt es die bequeme Abkürzung liftIO (Klasse MonadIO): Sie hebt eine IO-Aktion direkt in einen beliebig tiefen Stack, der auf IO aufbaut — unabhängig davon, wie viele Transformatoren dazwischenliegen.

Ein bewährtes Muster ist der ReaderT-Stack: ReaderT r IO als Grundgerüst einer Anwendung mit Konfiguration r, in dem Zustand, Fehlerbehandlung und Logging als weitere Schichten hinzukommen. Die Klasse MonadReader/MonadState erlaubt es, auf die jeweilige Fähigkeit zuzugreifen, ohne die genaue Stack-Tiefe zu kennen.

Monadentransformer sind der praktische Grund, warum die Monade in Haskell-Programmen allgegenwärtig ist: Sie machen kombinierte Effekte typsicher und bleiben dabei rein — ohne Ausnahmen und globale Variablen. Verwandte Grundlagen: Funktionale Programmierung, Typsystem, Haskell-Befehle.