0
votes

This question can be considered a follow-up of

Lift instance of class with a `MonadIO` type-variable to the transformed monad

Which provides an example of an application of where this would be used.

The idea is that a typeclass

class (Monad m) => Class m a where
  method :: a -> m ()

exist, with base instances in different monads

instance Class M A where 
  method = undefined

instance Class (T M) B where
  method = undefined

. What I want is a way to lift any instance to itself, or higher in the transformer stack, much like what liftIO does for IO. My initial Idea was to define a lifting instance

instance (MonadTrans t, Class m a) => Class (t m) a where
  method = lift . method 

This has the problem however of creating overlapping instances when more than one transformer is applied, as lift is polymorphic, and can be replaced by, for example, lift . lift.

It was suggested to instead use a similar default instance,

class (Monad m) => Class m a where
  method :: a -> m ()
  default method :: (m ~ t n, MonadTrans t, Class n a) => a -> m ()
  method = lift . method

that can then be used to declare lifting instances

instance (MonadTrans t) => Class (t M) A

instance (MonadTrans t) => Class (t (T M)) B

. This works, but lifting instances need to be declared for each base instance, so I am curious; is there any other way to solve this without resorting to overlapping instances?

1
Why do you need a Class (T M) B instance in the first place? Can't that case be covered by (MonadTrans t, Class m a) => Class (t m) a ?Fyodor Soikin
Or is the idea to limit T M to only B, but disallow the use of A with T M?Fyodor Soikin
The idea is that the implementation of method for B is in T M, and is impossible to define in just M.notBob
@notBob Can we assume that the set of base instances is "closed", that is, not extensible by users of the library?danidiaz
@danidiaz No, this can not be assumed. While I will likely be the only user of it, I intend to keep only the most general stuff in this module and define instances in other modules.notBob

1 Answers

2
votes

You write

instance (MonadTrans t) => Class (t M) A
instance (MonadTrans t) => Class (t (T M)) B

This works, but lifting instances need to be declared for each base instance[....]

That's not how those defaults are intended to be used. The instances should look like

instance Class m a => Class (StateT s m) a
instance Class m a => Class (MaybeT m) a

and so on.