Consider the following code, which typechecks:
module Scratch where
import GHC.Exts
ensure :: forall c x. c => x -> x
ensure = id
type Eq2 t = (forall x y. (Eq x, Eq y) => Eq (t x y) :: Constraint)
foo :: forall t a.
( Eq2 t
, Eq a
) => ()
foo = ensure @(Eq (a `t` a)) ()
foo isn't doing anything useful here, but let's imagine it's doing some important business that requires an Eq (t a a) instance. The compiler is able to take the (Eq2 t, Eq a) constraints and elaborate an Eq (t a a) dictionary, so the constraint is discharged and everything works.
Now suppose we want foo to do some additional work that depends on an instance of the following rather convoluted class:
-- some class
class (forall ob x y. (SomeConstraint t ~ ob, ob x, ob y) => ob (t x y)) =>
SomeClass t
where
type SomeConstraint t :: * -> Constraint
foo' :: forall t a.
( Eq2 t
, Eq a
, SomeClass t -- <- the extra constraint
) => ()
foo' = ensure @(Eq (a `t` a)) ()
Note that in the body of foo' we're still demanding only what we did in foo: an Eq (t a a) constraint. Moreover we haven't removed or modified the constraints the compiler used to elaborate an instance of Eq (t a a) in foo; we still demand (Eq2 t, Eq a) in addition to the new constraint. So I would expect foo' to typecheck as well.
Unfortunately, it looks like what actually happens is that the compiler forgets how to elaborate Eq (t a a). Here is the error we get in the body of foo':
• Could not deduce (Eq (t a a)) arising from a use of ‘ensure’
from the context: (Eq2 t, Eq a, SomeClass t)
bound by the type signature for:
foo' :: forall (t :: * -> * -> *) a.
(Eq2 t, Eq a, SomeClass t) =>
()
Given that the compiler can "deduce Eq (t a a)" just fine "from the context (Eq2 t, Eq a)", I don't understand why the richer context (Eq2 t, Eq a, SomeClass t) causes Eq (t a a) to become unavailable.
Is this a bug, or am I just doing something wrong? In either case, is there some workaround for this?
It's not really a bug; it's expected. In the definition of
foo, the context hasforall x y. (Eq x, Eq y) => Eq (t x y)(i.e.Eq2 t)Eq aSomeClass tforall ob x y. (SomeConstraint t ~ ob, ob x, ob y) => ob (t x y)(from the "closure of the superclass relation over"SomeClass t)We want
Eq (t a a). Well, from the context, there are two axioms whose heads match: (1) withx ~ a, y ~ aand (2) withob ~ Eq, x ~ a, y ~ a. There is doubt; GHC rejects. (Note that sinceSomeConstraint t ~ obis only in the hypothesis of (4), it gets completely ignored; choosing instances pays attention only to instance heads.)The obvious way forward is to remove (4) from the superclasses of
SomeClass. How? Split the quantification from the actual instance "head":This is what your
forall ob. _ => forall x y. _ => _trick basically did, except this doesn't rely on a bug (your syntax is not allowed). Now, (4) becomesforall ob x y. (SomeConstraint t ~ ob, ob x, ob y) => SomeClassSuper ob t x y. Because this is not actually a constraint of the formClass args..., it doesn't have superclasses, so GHC doesn't search upwards and find the all-powerfulforall ob x y. ob (t x y)head that ruins everything. Now the only instance capable of dischargingEq (t a a)is (1), so we use it.GHC does search the "superclasses" of the new (4) when it absolutely needs to; the User's Guide actually makes this feature out to be an extension to the base rules above, which come from the original paper. That is,
forall ob x y. (SomeConstraint t ~ ob, ob x, ob y) => ob (t x y)is still available, but it is considered after all the "true" superclasses in the context (since it isn't actually the superclass of anything).You might argue that, in accordance with the open world policy, GHC should backtrack in the face of "incoherence" (such as the overlap between (1) and the original (4)), since quantified constraints can manufacture "incoherence" while there is no actual incoherence and we'd like your code to "just work". That's a perfectly valid want, but GHC is currently being conservative and just giving up instead of backtracking for reasons of performance, simplicity, and predictability.