Search code examples
haskellmonadsmonad-transformers

Does MonadTransControl support STM?


I am looking for a standard way to use monad transformers inside STM.atomically. I think it sounds strange because all use cases I found so far just liftIO . atomically and pass bare "STM a" without wrapping it into any monad transformer. I guess, because an atomic action usually is not very complicated - just oneliner and it is easier and more efficient to pass arguments through local variables, but in my case transaction is big and I would like to pass the main stack smoothly.

After getting familiar with monad-control library I gravite to opinion that runInBase cannot unlift monad stack if source and result base monads are different, but I am not sure.

inNestedState :: MyData -> StateT MyState STM ()

loadCounterA :: MyData -> StateT MyState IO ()
loadCounterA md = do
   control $ \runInBase -> atomically (runInBase (inNestedState md))

First error

monad-control-demo.hs:29:4: error:
    • Couldn't match type ‘STM’ with ‘IO’
      Expected type: StateT MyState IO ()
        Actual type: StateT MyState STM ()
    • In a stmt of a 'do' block:
        control $ \ runInBase -> atomically (runInBase (inNestedState md))
      In the expression:
        do control
             $ \ runInBase -> atomically (runInBase (inNestedState md))
      In an equation for ‘loadCounterA’:
          loadCounterA md
            = do control
                   $ \ runInBase -> atomically (runInBase (inNestedState md))
   |
29 |    control $ \runInBase -> atomically (runInBase (inNestedState md))

Meanwhile I end up with limited but handy home made solution:

class MonadRebase m n where
    rebase :: (m a) -> (n a)

instance (MonadRebase m n) => MonadRebase (ReaderT r m) (ReaderT r n) where
    rebase t = ReaderT $ \r -> rebase (runReaderT t r)

instance (MonadRebase m n) => MonadRebase (StateT s m) (StateT s n) where
    rebase t = StateT $ \s -> rebase (runStateT t s)

instance MonadRebase STM IO where
    rebase = atomically

data MyState = MyState {
      msCounter1 :: TVar Int,
      msCounter2 :: TVar Int,
      ops :: Int
    }

inStm :: ReaderT Int (StateT MyState STM) ()
inStm = do
  rInt <- ask
  lift $ do
          mySt <- get
          modify (\st -> st {ops = rInt + ops st })
          lift $ do
            c1Val <- (readTVar (msCounter1 mySt))
            c2Val <- (readTVar (msCounter2 mySt))
            writeTVar (msCounter1 mySt) 0
            writeTVar (msCounter2 mySt) (c1Val + c2Val)

foo :: ReaderT Int (StateT MyState IO) ()
foo = do
  rebase inStm

Any ideas how to do the same with exiting libraries are appreciated.


Solution

  • I'm interpreting your question as "How can I convert a StateT MyState STM () to a StateT MyState IO ()?". The answer is mapStateT:

    loadCounterA = mapStateT atomically . inNestedState
    

    To go down multiple layers of a transformer stack, as in your second example, simply nest applications of the transformers' respective map functions:

    foo = mapReaderT (mapStateT atomically) inStm
    

    When you have a big transformer stack this can be a bit fiddly, but it's the sort of code you can't get wrong thanks to the type checker.