I am writing a project that involves composing several stacks of StateT and ReaderT monads:
newtype FooT m a = FooT { unFooT :: (StateT State1 (ReaderT Reader1 m)) a }
newtype BarT m a = BarT { unBarT :: (StateT State2 (ReaderT Reader2 m)) a }
Then, I basically just run everything in FooT (BarT m) and lift into the appropriate monad as necessary. I'm using lens to interact with the various state/reader types:
foo :: Monad m => FooT m ()
foo = do
field1 .= ... -- where field1 is a lens into State1
...
However, this approach gets ugly as I add more StateT + ReaderT transformers (and seems like it might incur some performance costs).
My only idea so far is to combine the states like:
newtype BazT m a = BazT { unBazT :: StateT (State1, State2) (ReaderT (Reader1, Reader2) m)) a }
and then I can just project into the state types with more lenses.
foo :: Monad m => BazT m ()
foo = do
(_1 . field1) .= ... -- where field1 is a lens into State1
...
Is there a canonical way to combine multiple states like this? If possible I'd like to avoid modifying all the lens code.