I structure a computation around the use of the RWS
(Reader
+Writer
+State
) monad:
newtype Problem a = Problem { unProblem :: RWS MyEnv MyLog MyState a }
deriving ({- lots of typeclasses -})
The computation is built step by step by assembling elementary computations of the form
foo :: a -> Problem b
Sometimes, however, the subcomputations do not need the full power of the RWS
monad. For instance, consider
bar :: c -> State MyState d
I would like to use bar
as a part of a larger computation in the context of the Problem
monad. I can see three ways of doing this, none of which seems very elegant to me.
Manually unwrap the the
State
computation and rewrap it in the RWS monad:baz :: a -> RWS MyEnv MyLog MyState c baz x = do temp <- foo x initialState <- get let (finalResult, finalState) = runState (bar temp) initialState put finalState return finalResult
Modify the type signature of
bar
by lifting it into theProblem
monad. This has the downside that the new type signature does not explicitly promise thatbar
is independent ofMyEnv
and logs nothing toMyLog
.Replace the
RWS
monad by an explicitReaderT MyEnv WriterT MyLog State MyState
monad stack. This allows me to conciselylift.lift
thebar
subcomputation into the full monad; however, this trick will not work e.g. for a subcomputation of the formc -> Reader MyEnv d
.
Is there a cleaner way to compose foo
and bar
? I have a hunch that a few clever definitions of type-class instances might do the trick, but I can't see exactly how to proceed.