I am currently learning to use Monad-Transformers in Haskell. I have composed a simple
Monad-Transformer using StateT
and ExceptT
:
type DatabaseT m = StateT DatabaseState (ExceptT DatabaseError m)
There are already some functions working with DatabaseT
like:
connectDB :: MonadIO m => DBMode -> DatabaseT m ()
isConnected :: Monad m => DatabaseT m Bool
To make lifting of such functions easier I want to define a type-class
similar to MonadIO
:
class Monad m => MonadDB m where
liftDB :: Monad m' => DatabaseT m' a -> m a
Now the problematic part is making DatabaseT m
an instance if MonadDB
.
Looking at the implementation of MonadIO
this function should be the identity function:
instance Monad m => MonadDB (DatabaseT m) where
liftDB = id
However this code does not compile:
Couldn't match type ‘m'’ with ‘m’
‘m'’ is a rigid type variable bound by
the type signature for:
liftDB :: forall (m' :: * -> *) a.
Monad m' =>
DatabaseT m' a -> DatabaseT m a
‘m’ is a rigid type variable bound by
the instance declaration
Expected type: DatabaseT m' a -> DatabaseT m a
Actual type: DatabaseT m a -> DatabaseT m a
In the expression: id
In an equation for ‘liftDB’: liftDB = id
In the instance declaration for ‘MonadDB (DatabaseT m)’
Relevant bindings include
liftDB :: DatabaseT m' a -> DatabaseT m a
It seems having a Monad-Transformer type-class for a Monad MyMonad
, with liftMyMonad
like
functions, does not work when MyMonad
is a Monad-Transformer itself.
Is this a valid approach to make lifting of DatabaseT m
actions more generic or
is it fundamentally flawed?
If so, how can I lift DatabaseT m
actions from anywhere on top of a DatabaseT
stack?
CodePudding user response:
The end state you ask for probably isn't the end state you want. Namely this is not great for most needs:
class Monad m => MonadDB m where
liftDB :: Monad m' => DatabaseT m' a -> m a
The typical solution is to not write such an instance at all and instead write:
class Monad m => MonadDB m where
connectDB :: DBMode -> m ()
isConnected :: m Bool
This allows you to:
- Use the functions in monadic contexts where DatabaseT is a component
- Use solutions like MonadMock for testing
- Not break abstraction - avoid using
lift
in the consumer code at all.