I'm trying to express an idea that given
instance (MonadTrans t, MonadX m) => MonadX (t m)
it should follow, that any t1 (t2 ... (tn m))
is also MonadX
as long as all tx
have MonadTrans
instance. However when i try to write it down it doesn't work:
{-# LANGUAGE BasicallyEverything #-}
data Dict c where
Dict :: c => Dict c
class (forall m . Monad m => Monad (t m)) => MonadTrans t where
lift :: Monad m => m a -> t m a
class (c m, Monad m, forall t . (MonadTrans t, Monad (t m)) => c (t m)) => Foo c m
instance (c m, Monad m, forall t . (MonadTrans t, Monad (t m)) => c (t m)) => Foo c m
liftDict :: MonadTrans t => Dict (Foo c m) -> Dict (Foo c (t m))
liftDict Dict = Dict
This results in following error:
• Could not deduce: c (t1 (t m)) arising from a use of ‘Dict’
from the context: MonadTrans t
bound by the type signature for:
liftDict :: forall (t :: (* -> *) -> * -> *)
(c :: (* -> *) -> Constraint) (m :: * -> *).
MonadTrans t =>
Dict (Foo c m) -> Dict (Foo c (t m))
at src/Lib.hs:85:1-64
or from: Foo c m
bound by a pattern with constructor:
Dict :: forall (a :: Constraint). a => Dict a,
in an equation for ‘liftDict’
at src/Lib.hs:86:10-13
or from: (MonadTrans t1, Monad (t1 (t m)))
bound by a quantified context at src/Lib.hs:1:1
• In the expression: Dict
In an equation for ‘liftDict’: liftDict Dict = Dict
• Relevant bindings include
liftDict :: Dict (Foo c m) -> Dict (Foo c (t m))
(bound at src/Lib.hs:86:1)
|
86 | liftDict Dict = Dict
Is there any way to make it work?
You get the exact same error with the slightly simpler definition of Foo c m
given here:
(c m, Monad m, forall t. MonadTrans t => c (t m))
^^^ don't really need Monad (t m) here
Let's clarify some of the variable names, so that it's clear which variables are definitely equal and which aren't when writing liftDict
.
Have:
MonadTrans t
forall m'. Monad m' => Monad (t m')
Foo c m
c m
Monad m
forall t'. MonadTrans t' => c (t' m)
Want:
Foo c (t m)
c (t m)
Monad (t m)
forall t''. MonadTrans t'' => c (t'' (t m))
In the "want" category, c (t m)
is easy -- we apply forall t'. MonadTrans t' => c (t' m)
to our t'~t
and MonadTrans t
. The Monad (t m)
is easy, too, by applying forall m'. Monad m' => Monad (t m')
to m'~m
and Monad m
.
But that last one... is tricky! You want these to line up:
Have: forall t' . MonadTrans t' => c (t' m )
Want: forall t''. MonadTrans t'' => c (t'' (t m))
But they don't quite line up. What's happened here is that m
is a fixed monad, not an argument that we can specialize to our new choice of t m
! Okay, so let's make it an argument.
class (c m, Monad m, forall m' t . (Monad m', MonadTrans t) => c (t m')) => Foo c m
instance (c m, Monad m, forall m' t . (Monad m', MonadTrans t) => c (t m')) => Foo c m
This compiles! But it no longer says what we wanted, because the inductive step we've said here doesn't demand a c
constraint. Luckily, we can add that back in without causing it to stop compiling:
class (c m, Monad m, forall m' t . (c m', Monad m', MonadTrans t) => c (t m')) => Foo c m
instance (c m, Monad m, forall m' t . (c m', Monad m', MonadTrans t) => c (t m')) => Foo c m
I think you might find it intuitive to group these constraints slightly differently:
class ( (c m, Monad m) -- base case
, forall m' t. (c m', Monad m', MonadTrans t) => c (t m')
-- inductive hypothesis
) => Foo c m
But beware: this Foo
probably has fewer instances than you think at first. In particular, for there to be a Foo c
instance, there can be only one, fully general, instance of c
for type-level applications.