This is one of the technical problems with which the mtl library was developed. It looks like you are already using it, but you are not aware of its full potential.
The idea is to simplify the operation of transformer stacks using types. The problem with regular monad transformer stacks is that you need to know all the transformers you use when you write a function, and changing the transformer stack changes how elevators work. mtl solves this by defining a class for each transformer that it has. This allows you to write functions that have a class restriction for each transformer required, but can work with any transformer stack that includes at least those.
This means that you can freely write functions with various sets of restrictions, and then use them in your game monad, if your game monad has at least these features.
For example, you may have
erk :: MonadRandom m => ... incr :: MonadState GameState m => ... err :: MonadError GameError m => ... lots :: (MonadRandom m, MonadState GameState m) => ...
and define a Game a type to support all of these:
type Game a = forall g. RandT g (StateT GameState (ErrorT GameError IO)) a
You can use all of these interchangeable elements within the Game because the Game belongs to all these classes. Moreover, you do not need to change anything except the definition of Game if you want to add additional features.
There is one important limitation: you can access only one instance of each transformer. This means that you can only have one StateT and one ErrorT in your stack. That's why StateT uses its own GameState type: you can just put all the different things you can store in your game into one type so you only need one StateT . ( GameError does the same for ErrorT .)
For such code, you can simply use the Game type directly when defining your functions:
flipCoin :: Game a -> Game a -> Game a flipCoin ab = ...
Since getRandom is polymorphic over m , it will work with any Game if it has at least RandT (or something similar) inside.
So, to answer your question, you can simply rely on existing mtl classes to take care of this. All primitive operations, such as getRandom , are polymorphic compared to their monad, so they will work with any stack at the end of which you finish. Just wrap all your transformers in your own ( Game ) and everything will be installed.