पाठ 17 / 25
Monad Transformers and Application Structure
Combine Reader, State and IO with mtl, and know the alternatives.
Stacking effects
Real applications need several effects at once: read configuration, keep state, log and perform I/O. Monad transformers stack these capabilities: ReaderT Config IO adds a read-only environment to IO, StateT s m adds state, ExceptT e m adds typed errors, and WriterT adds output (though it is rarely recommended because of space leaks). The mtl library provides type classes such as MonadReader, MonadState, MonadError and MonadIO so functions can state the capabilities they need ((MonadReader Config m, MonadIO m) => m Int) and ask, get and liftIO work through the stack without manual lifting. A popular, simple architecture is the ReaderT design pattern: a single ReaderT Env IO with an environment record holding configuration, connection pools and mutable references (IORef, TVar), avoiding deep stacks. Effect system libraries (such as effectful, polysemy or bluefin) offer alternatives with different trade-offs. Keep business logic pure where possible and use the application monad only for orchestration.
A small application monad with mtl
ReaderT for configuration over StateT for a log over IO.
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Reader (ReaderT, ask, runReaderT)
import Control.Monad.State (StateT, modify, runStateT)
data Config = Config { gstPercent :: Int, freeShippingFrom :: Int }
type App = ReaderT Config (StateT [String] IO)
logMsg :: String -> App ()
logMsg m = modify (++ [m]) -- mtl lifts this through ReaderT automatically
priceOrder :: Int -> App Int
priceOrder subtotal = do
cfg <- ask
let gst = subtotal * gstPercent cfg `div` 100
shipping = if subtotal >= freeShippingFrom cfg then 0 else 4900
logMsg ("gst " ++ show gst ++ ", shipping " ++ show shipping)
liftIO (putStrLn ("priced order of " ++ show subtotal))
pure (subtotal + gst + shipping)
main :: IO ()
main = do
let cfg = Config { gstPercent = 18, freeShippingFrom = 100000 }
(total, logs) <- runStateT (runReaderT (priceOrder 50000) cfg) []
print total -- 63900
mapM_ putStrLn logs -- gst 9000, shipping 4900Layers of a delivery bag
A transformer stack is a delivery bag with layers: the outer pocket holds the address card (Reader), the middle a running receipt (State), and the core is the actual trip through the city (IO). Each layer adds a capability without the others needing to know about it.
त्वरित जाँच: What does ReaderT Config IO add to plain IO?
- Mutable state
- Access to a shared, read-only Config value via ask
- Exceptions
- Parallelism
Answer
Access to a shared, read-only Config value via ask — ReaderT threads an environment through computations implicitly.