home Funktionale Programmierung: Error-, Reader-, Writer- und State-Monade Prof. Dr. Uwe Schmidt FH Wedel

Error-, Reader-, Writer- und State-Monade

weiter

weiter

Fehler-Monade

MonadError
für Berechnungen, die fehlschagen können und eine definierte Fehlermeldung liefern, anstatt das Programm abzubrechen (error)
typische Anwendung
Berechnungen, die nicht immer ein sinnvolles Resultat liefern
Typ-Klasse
definiert in Control.Monad.Error oder Control.Monad.Except (mtl >= 2.2)
 
class (Monad m) => MonadError e m | m -> e where
    throwError :: e -> m a
    catchError :: m a -> (e -> m a) -> m a
merke
MonadError ist eine Typklasse mit mehr als einem Parameter (multi parameter type class), einer Erweiterung aus dem Haskell2010-Standard.
merke
Bei Typklassen mit mehreren Typparametern werden an manchen Stellen funktionale Abhängigkeiten (functional dependencies) notwendig, auch eine Erweiterung aus dem Haskell2010-Standard.
einfache Implementierung
-- in Control.MonadError
-- schon vordefiniert
 
data Either e a
  = Left  e
  | Right a
 
instance Monad (Either e) where
  return = Right
 
  Left  e >>= f = Left e
  Right x >>= f = f x
 
instance MonadError e (Either e) where
  throwError = Left
 
  Right x `catchError` h = Right x
  Left  e `catchError` h = h e
Beispiel
import Control.Monad.Except
 
type Comp a = Either String a
 
divide :: Int -> Int -> Comp Int
divide x 0
  = throwError "divide by zero"
divide x y
  = return $ x `div` y
 
catchDivide :: Int -> Int -> Comp Int
catchDivide x y
  = divide x y `catchError` (\ e -> return 0)
 
t1, t2, t3 :: Comp Int
t1 = 2 `divide` 1
t2 = 2 `divide` 0
t3 = 2 `catchDivide` 0

weiter

Reader-Monade

Literatur
 
weiter
Reader
für Berechnungen, die von einem nur zu lesenden Kontext abhängen
weiter
typische Anwendung
Konfigurations-Parameter, Optionen, Umgebungsvariablen
Typ-Klasse
definiert in Control.MonadReader
 
class Monad m => MonadReader r m | m -> r where
    ask   :: m r
    local :: (r -> r) -> m a -> m a
 
asks :: MonadReader r m => (r -> a) -> m a
asks f = ask >>= return . f
einfache Implementierung
import Control.Monad.Reader
 
newtype Reader r a = Reader { runReader :: r -> a }
 
instance Monad (Reader r) where
  return a
    = Reader $ \ r -> a
 
  m >>= k
    = Reader $ \ r -> runReader (k (runReader m r)) r
 
instance MonadReader (Reader r) where
  ask
    = Reader $ \ r -> r   -- id
 
  local f m
    = Reader $ \ r -> runReader m (f r)
merke
MonadReaderist wieder eine Typklasse mit mehr als einem Parameter (multi parameter type class) und mit einer funktionalen Abhängigkeit (functional dependency).
Beispiel
import Control.Monad.Reader
 
data Options = Options
  { filename :: String
  , verbose  :: Bool
  } deriving (Show)
 
computation :: Reader Options (Maybe String)
computation
  = do v <- asks verbose
       n <- asks filename
       if v
         then return (Just n)
         else return Nothing
 
ex1 :: Maybe String
ex1 = runReader computation $ Options "hello" True
 
ex2 :: Maybe String
ex2 = runReader computation $ Options "world" False

weiter

Writer-Monade

Writer
für Berechnungen, die während der Ausführung zusätzliche Informationen sammeln
typische Anwendung
Logging, Tracing, Berechnungen mit mehreren Resultaten
Typ-Klasse
definiert in Control.Monad.Writer
 
class (Monoid w, Monad m) => MonadWriter w m | m -> w where
    tell   :: w -> m ()
merke
MonadWriter ist wieder eine Typklassen mit mehr als einem Parameter.
einfache Implementierung
import Control.Monad.Writer
import Data.Monoid
 
newtype Writer w a = Writer { runWriter :: (a, w) }
 
instance Monoid w => Monad (Writer w) where
  return a
    = Writer (a, mempty)
 
  m >>= k
    = Writer $ let (a, w1) = runWriter m
                   (b, w2) = runWriter (k a)
               in (b, w1 <> w2)
 
instance MonadWriter (Writer w) where
  tell w
    = Writer $ ((), w)
 
-- ignore explicit result
execWriter :: Writer w a -> w
execWriter = snd . runWriter
Beispiel
{-# LANGUAGE FlexibleContexts #-}
module DivLog where
 
import Control.Monad.Writer
 
type Logger a = Writer [String] a
 
divide :: Int -> Int -> Logger Int
divide x 0
  = do tell ["division by zero"]
       return 0
divide x y
  = do tell [show x ++ " div " ++ show y]
       let r = x `div`y
       tell ["result = " ++ show r]
       return r
 
run1 :: (Int, [String])
run1 = runWriter (divide 1 0)
 
run2 :: (Int, [String])
run2 = runWriter (divide 5 2)
 
run3 :: (Int, [String])
run3 = runWriter (divide 5 2 >> divide 1 0)

weiter

Zustands-Monade

State
für Berechnungen, die während der Ausführung einen zusätzlichen veränderbaren Zustand mit sich führen
typische Anwendung
Simulation globaler Variablen
Interpretierer für imperative Sprachen
Typ-Klasse
definiert in Control.Monad.State
 
class Monad m => MonadState s m | m -> s where
    get :: m s
    put :: s -> m ()
 
modify :: MonadState s m => (s -> s) -> m ()
modify f
  = do s <- get
       put (f s)
 
gets :: MonadState s m => (s -> a) -> m a
gets f
  = do s <- get
       return (f s)
 
-- ohne do-Notation
 
modify f = get >>= put    . f
gets   f = get >>= return . f
gets   f = f <$> get
merke
MonadStateist wieder eine Typklasse mit mehr als einem Parameter (multi parameter type class) und mit einer funktionalen Abhängigkeit (functional dependency).
einfache Implementierung
import Control.Monad.State
 
newtype State s a = State { runState :: s -> (a,s) }
 
instance Monad (State s) where
  return a
    = State $ \ s -> (a, s)
 
  m >>= k
    = State $ \ s -> let (a, s') = runState m s
                     in runState (k a) s'
 
instance MonadState s (State s) where
  get
    = State $ \ s -> (s, s)
 
  put s
    = State $ \ _ -> ((), s)
 
-- state only used during computation
evalState :: State s a -> s -> a
evalState act = fst . runState act
 
-- state contains the "real" result
execState :: State s a -> s -> s
execState act = snd . runState act
Beispiel
test :: State Int Int
test = do
  put 3
  modify (+1)
  get
 
t1 :: Int
t1 = execState test 0
?
Ist IO eine Instanz von MonadState?

weiter

Monade mit Auswahl und Scheitern

 
class Monad m => MonadPlus m where
  mzero :: m a
  mplus :: m a -> m a -> m a
Gesetze
  mzero >>= f  =  mzero
  v >> mzero   =  mzero
 
  c1 `mplus` (c2 `mplus` c3)
=
  ( c1 `mplus` c2) `mplus` c3
Instanz
für Maybe
 
instance MonadPlus Maybe where
    mzero             = Nothing
    Nothing `mplus` x = x
          x `mplus` _ = x
Beispiel
mit den oben verwendeten Funktionen
 
choice x
  = f1 x `mplus` f2 x `mplus` f3 x
merke
Der erste gewinnt!
?
Instanz von MonadPlus für Listen ([.])?

Letzte Änderung: 30.09.2020
© Prof. Dr. Uwe Schmidt
Prof. Dr. Uwe Schmidt FH Wedel