Effectful Conventions
Dispatch Choice
- •Default: Static (
Effectful.Reader.Static,Effectful.State.Static.Local) - •Only use Dynamic when MonadReader/MonadState instances are needed
Effect Stack Order
Always consistent: Reader -> State -> Error -> IOE
haskell
runApp :: Config -> AppState -> App a -> IO (Either (CallStack, AppError) (a, AppState)) runApp cfg st = runEff . runError . runStateLocal st . runReader cfg
Custom Effects
- •First-order (no
mparameter): useinterpret_ - •Higher-order (uses
mparameter): useinterpret+localSeqUnlift
Error Handling
- •Use constrained effect signatures so caller decides error handling
- •Error.Static lacks MonadError -- define local
liftEitherhelper - •Use
runErrorNoCallStackorrunErrorWithat boundaries
Concurrency
ALWAYS use Effectful.Concurrent.*, NEVER Control.Concurrent.* with liftIO.
Anti-patterns
- •Adding
Errorconstraint to interpreter instead of effect definition - •Returning raw
Eitherwhen errors should propagate via effect - •Mixing Error.Static and Error.Dynamic
- •Over-constraining effect signatures
- •Using
Control.Concurrent.*withliftIOinstead ofEffectful.Concurrent.*
Reference
For detailed code examples (dependency injection, testing, MTL migration, concurrency):
- •
references/effectful-examples.md