Lift to fix the *inside* of a monad transformer stack

514 views Asked by At

Suppose I have an IO Int wrapped in a StateT MyState, then I have a value of State MyState Int which I want to use in the stacked monad. How do I lift it in this inner sense? I already know to use lift or liftIO if I get something compatible with the inside that I just need to lift to the outside monad, but now I have the opposite problem: the value is already in the outer monad but not the inner one.

For instance:

checkSame :: State MyState a -> IO a -> StateT MyState IO Bool
checkSame sim real = do
  rres <- liftIO real
  sres <- ??? sim 
  return $ rres == sres

Do I have to 'get' the state, shove it through runState by hand and box it all up again, or is there some generic way to do this?

BTW, that sim parameter is a whole bunch of stateful functions that have nothing to do with IO, so I'm a bit reluctant to make them all return StateT MyState IO a if I can avoid it.

1

There are 1 answers

2
Daniel Wagner On BEST ANSWER

You have two options:

  1. Find a monad morphism. This is often a matter of finding the right library; in this case hoist and generalize together should get you where you need to go.
  2. Make your State action more polymorphic. This is the commonly used one, and the recommended one; it amounts to pre-applying the morphism from part 1, but has a lot of machinery put in place already in the mtl library to make it easy. The idea here is that if you write your State action just in terms of get, put, and modify, then instead of the type State s a, you can give it the type:

    MonadState s m => m a
    

    Then later, at the call site, you can choose whatever monad is appropriate for this, including both State s a and StateT s IO a. Moreover, since it specializes to the type State s a, you can be sure it doesn't do any IO or anything like that that State s a itself couldn't do, so you get the same behavioral guarantees.