Monad Transformers and mtl
26.1 Stacking Effects
import Control.Monad.State
import Control.Monad.Except
import Control.Monad.Reader
-- ReaderT Config (StateT Int (ExceptT String IO)) a
data Config = Config {maxAttempts :: Int}
type App = ReaderT Config (StateT Int (ExceptT String IO))
attempt :: String -> App ()
attempt guess = do
limit <- asks maxAttempts
n <- get
when (n >= limit) $ throwError "too many attempts"
put (n + 1)
liftIO (putStrLn ("Attempt " ++ show (n + 1) ++ ": " ++ guess)) -- lift IO into the stack
runApp :: App a -> IO (Either String (a, Int))
runApp app = runExceptT (runStateT (runReaderT app (Config 2)) 0)
main :: IO ()
main = runApp (mapM_ attempt ["a", "b", "c"]) >>= print
-- Attempt 1: a / Attempt 2: b / Left "too many attempts"26.2 mtl-Style Constraints
MonadReader (ask, asks, local) MonadState (get, put, modify, gets) MonadWriter (tell)
MonadError (throwError, catchError) Failure without information MonadIO (liftIO)
Order matters: StateT s (ExceptT e m) loses the state on error; ExceptT e (StateT s m) keeps it.
Alternatives: ReaderT-over-IO pattern (Module 51), effect systems like effectful, polysemy, fused-effects, bluefin (Module 42).
{-# LANGUAGE FlexibleContexts #-}
-- Write functions against capabilities, not concrete stacks
incrementCounter :: (MonadState Int m, MonadIO m) => m ()
incrementCounter = do
modify (+ 1)
n <- get
liftIO (print n)
validate :: MonadError String m => Int -> m Int
validate x = if x < 0 then throwError "negative" else pure x
-- Works in any stack providing those capabilities