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.
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.