So I tried to ask a general "how do you debug type-level programming problems?" question, and it seems either the question is too general to answer, or maybe it's impossible to debug such things. :-}
On this particular day, the problem that's irking me is a moderately large, complex program. I've managed to boil it down to a reasonably small core that still goes wrong. Here we go:
{-#
LANGUAGE
GADTs, MultiParamTypeClasses,
FlexibleInstances, FlexibleContexts, RankNTypes,
KindSignatures
#-}
module Minimal where
-------------------------------------------------------------------------------
data Union (t :: * -> *) (ts :: * -> *) x
class Member (x :: * -> *) (ys :: * -> *) where
instance Member x (Union x ys) where
instance (Member x ys) => Member x (Union y ys) where
-------------------------------------------------------------------------------
data ActM (effects :: * -> *) x
instance Monad (ActM effects) where
-------------------------------------------------------------------------------
data Reader i x where
Get :: Reader i i
get :: Member (Reader i) req => ActM req i
get = undefined
runReader :: i -> ActM (Union (Reader i) req) x -> ActM req x
runReader = undefined
(Aside: I look at that list of language extensions and think to myself "maybe this is just a terrible idea!")
Anyway, the really interesting part is at the bottom. We see that
runReader :: i -> ActM (Union (Reader i) req) x -> ActM req x
In other words, for any i, runReader takes an action in the ActM monad that has Reader i as one of its possible effects, and returns an action in the ActM monad without that possible effect. Simple, right?
Now if I pass (say) a Char as the first argument, then the reader type is fixed to Char:
runReader 'X' :: ActM (Union (Reader Char) req) x -> ActM req x
So far, so good. If I just return some data, all is fine:
runReader 'X' (return True) :: ActM req Bool
(Reassuringly, I also get the correct value as the result!)
But now, what about the get function?
get :: Member (Reader i) req => ActM req i
So get is an action in the ActM monad for any set of effects that includes Reader i. And that means that if I pass it to runReader then I get...
runReader 'X' get :: Member (Reader x) (Union (Reader Char) req) => ActM req x
...wait, what?!? Why hasn't the compiler figured out that x must be Char?
Indeed, if I add an explicit type signature that says
runReader 'X' get :: ActM req Char
then it compiles perfectly (and, incidentally, I get the right value output, which is nice). So where am I missing a constraint?
Member x (Union x ys)requires that both thexs be the same to begin with to match - the compiler will not deduce this information. You would have to write(x ~ x') => Member x (Union x' xs)but this would be overlapping with the other instance - I believe you need a cleverer way of computing membership. - user2407038Memberinstances look awfully suspicious -- like maybe you'll needOverlappingInstancesor perhaps evenIncoherentInstancesfor those to "work". If you've gotIncoherentInstanceson, well... I think the official party line with that extension is, "Good luck, don't complain to us.". - Daniel WagnerOverlappingInstancesis supposed to be in there. I'm not sure how it ended up missing. I must have accidentally dropped it while building my minimal example. - MathematicalOrchidx ~ yin a type signature! You learn something new every day... - MathematicalOrchid