I asked this question on StackOverflow, got some answers, most notably a link to this one, and basing on that I've implemented this:{-# LANGUAGE RankNTypes #-}{-# LANGUAGE FlexibleContexts #-}module Main whereimport Control.Monad.Stateimport Control.Monad.IO.Class-- Module----------------------------------------------------------------------------------------newtype Module m a b =    Module (a -> m (b, Module m a b)){-instance (Monad m) => Applicative (Module m a)instance (Monad m) => Arrow (Module m)instance (Monad m) => Category (Module m)instance (Monad m) => Functor (Module m a)-}-- GraphicsModule----------------------------------------------------------------------------------------data GraphicsState = GraphicsState Intrender :: (MonadState GraphicsState m, MonadIO m) => Int -> m ()render x = do    (GraphicsState s) <- get    liftIO $ print $ x + s    put . GraphicsState $ s + 1type GraphicsModule = Module IO Int ()initialGraphicsState = GraphicsState 0createGraphicsModule :: GraphicsState -> GraphicsModule createGraphicsModule initialState = Module $ \x -> do    (r, s') <- runStateT (render x) initialState    return (r, createGraphicsModule s') initialGraphicsModule = createGraphicsModule initialGraphicsStaterunModule (Module m) x = m x-- Program----------------------------------------------------------------------------------------data ProgramState = ProgramState {    graphicsModule :: GraphicsModule}renderInProgram :: (MonadState ProgramState m, MonadIO m) => Int -> m ()renderInProgram x = do    gm <- gets graphicsModule    (r, gm') <- liftIO $ runModule gm x    modify $ \g -> g { graphicsModule = gm' }initialProgramState = ProgramState initialGraphicsModulemain = runStateT prog initialProgramStateprog = do    renderInProgram 1    renderInProgram 1    renderInProgram 1I can see how this could be quite easily extended to allow more functions in a module (instead of just render). I am not sure if I'm keeping the state correctly, though. That was the only way I saw to not expose the inner, stateful context (note that the outer monad to the module is just IO).Also I am aware of the fact that Lens could make it less verbose. I deliberately chose to not depend on Lens, and I think it's really functionally equivalent.
Polymorphic components for graphics and program state
haskell;polymorphism;monads;state