Recursively Run Transformers to Base Monad

Is there an interface class that allows one to run the transformers to the base monad?

For example something like

class Run a b c | a -> c where
  run :: a -> b -> c

instance Run (m (a, s)) b c => Run (StateT s m a) (s,b) c where
  run x (s,b) = run (runStateT x s) b

instance Run (IO a) b (IO a) where
  run = const

run (gets succ :: StateT Int (StateT String IO) Int) (1, ("abc", ()))
  :: IO ((Int, String), Int)
-- ((2, "abc"), 1)

EDIT ADDITION:

I have improved upon the Run class with RunBase below. There could be more general instances without TestParameters (test

class RunBase m b where
  type BaseMonad m :: Type -> Type
  type StM' m a :: Type
  runBase :: m a -> b -> BaseResult m a

data TestParameters r s = TestParameters
  { filePath :: FilePath
  , string :: String
  , read :: r
  , state :: s
  }

instance RunBase m b => RunBase (IdentityT m) b where
  type BaseMonad (IdentityT m) = BaseMonad m
  type StM' (IdentityT m) a = StM' m a
  runBase x b = runBase (runIdentityT x) b

instance RunBase m (TestParameters r s) => RunBase (ReaderT r m) (TestParameters r s) where
  type BaseMonad (ReaderT _ m) = BaseMonad m
  type StM' (ReaderT _ m) a = StM' m a
  runBase x b@(TestParameters {read}) = runBase (runReaderT x read) b

instance RunBase m (TestParameters r s) => RunBase (LazyWriterT w m) (TestParameters r s) where
  type BaseMonad (LazyWriterT _ m) = BaseMonad m
  type StM' (LazyWriterT w m) a = StM' m (a, w)
  runBase x b = runBase (Control.Monad.Trans.Writer.Lazy.runWriterT x) b

instance RunBase m (TestParameters r s) => RunBase (LazyStateT s m) (TestParameters r s) where
  type BaseMonad (LazyStateT s m) = BaseMonad m
  type StM' (LazyStateT s m) a = StM' m (a, s)
  runBase x b@(TestParameters {state}) = runBase (Control.Monad.Trans.State.Lazy.runStateT x state) b

instance RunBase m (TestParameters r s) => RunBase (LazyRWST r w s m) (TestParameters r s) where
  type BaseMonad (LazyRWST _ _ _ m) = BaseMonad m
  type StM' (LazyRWST _ w s m) a = StM' m (a, s, w)
  runBase x b@(TestParameters {read, state}) = runBase (Control.Monad.Trans.RWS.Lazy.runRWST x read state) b

instance RunBase IO b where
  type BaseMonad IO = IO
  type StM' IO a = a
  runBase = const
1 Like

Yes, MonadBaseControl from the monad-control package. But it has issues. Demystifying MonadBaseControl is a good article about it.

1 Like

Thank you for the suggestion. I had been looking in monad-control but I only saw ways of inserting computations in monads and transformers and not unwrapping them to the base monad.

Demystifying MonadBaseControl seems to validate my conclusion with:

  1. Capture the action’s input state and close over it.
  2. Package up the action’s output state with its result and run it.
  3. Restore the action’s output state into the enclosing transformer.
  4. Return the action’s result.

Maybe I am missing something, what should I be looking at specifically in monad-control?

Seems I misunderstood your question a little. Looks like you want a variation of MonadBaseControl that instead of giving you RunInBase as a scoped function simply has a function with a signature like RunInBase as a class method?

If so, unfortunately I don’t know of a library that provides that.

Monad transformers are just newtypes of their runners; is coerce not good enough for you?

import Control.Monad.Trans.State
import Data.Coerce

main :: IO ()
main = print =<< do
  coerce (gets succ :: StateT Int (StateT String IO) Int) (1 :: Int) "abc"
    :: IO ((Int, Int), String)
-- ((2,1),"abc")
1 Like

I think you just want monadBase from transformers-base

https://hackage.haskell.org/package/transformers-base-0.4.6/docs/Control-Monad-Base.html

Cool point! Didn’t think of coerce. I think this would work really well for most cases where one has picked a singular type.

Unfortunately, I am using this in tests (related to Type List that Holds Poly Kinded Types and it must work with many different transformers. Meaning when running coerce there will be differing numbers of inputs.

MonadBase seems to be similar to MonadBaseControl from monad-control. From what I see it is about lifting computations from the base monad up into layered transformers.

Perhaps I missed it, but I did not see an interface class for running the layered transformers. It should return a function that takes the uncurryed inputs for the transformers and returns the result in the base monad.

Edited the post to include an improved version of Run