As others have found out, the key to the problem is an existentially quantized tag in type Con3 . When you try to identify
Con3 s == Con3 t = ???
there is no reason s and t should be expressions with the same tag .
But maybe you don't care? You can perfectly define a heterogeneous equality test that gladly compares the Expr construct, regardless of tags.
instance Eq (Expr tag) where (==) = heq where heq :: Expr a -> Expr b -> Bool heq (Con1 i) (Con1 j) = i == j heq (Con2 s) (Con2 t) = heq st heq (Con3 s) (Con3 t) = heq st
If you don't care, then you might be advised to equip Con3 with a Con3 time witness to the existentially quantified tag . The standard way to do this is with a singleton design.
data SingExprTag (tag :: ExprTag) where SingTag1 :: SingExprTag Tag1 SingTag2 :: SingExprTag Tag2
Analysis of the case on the value in the SingExprTag tag will determine exactly what a tag . We can skip this additional information in Con3 as follows:
data Expr' (tag :: ExprTag) where Con1' :: Int -> Expr' tag Con2' :: Expr' tag -> Expr' tag Con3' :: SingExprTag tag -> Expr' tag -> Expr' Tag2
Now we can check if the tags match. We could write heterogeneous equality for singleton tags like this ...
heqTagBoo :: SingExprTag a -> SingExprTag b -> Bool heqTagBoo SingTag1 SingTag1 = True heqTagBoo SingTag2 SingTag2 = True heqTagBoo _ _ = False
... but to do this would be completely useless, since it gives us only a value like Bool , not Bool what that value means and what its truth can give us. Knowing that heqTagBoo ab = True does not tell typechecker anything useful about tags that a and b are witnesses to. The boolean bit is uninformative.
We can write essentially the same test, but in the positive case we prove that the tags are equal.
data x :=: y where Refl :: x :=: x singExprTagEq :: SingExprTag a -> SingExprTag b -> Maybe (a :=: b) singExprTagEq SingTag1 SingTag1 = Just Refl singExprTagEq SingTag2 SingTag2 = Just Refl singExprTagEq _ _ = Nothing
Now we are cooking gas! We can implement an Eq instance for Expr' that uses the ExprTag comparison to justify the recursive call for two children of Con3' when the tags were shown as matching.
instance Eq (Expr' tag) where Con1' i == Con1' j = i == j Con2' s == Con2' t = s == t Con3' as == Con3' bt = case singExprTagEq ab of Just Refl -> s == t Nothing -> False
The general situation is that advanced types require singleton types associated with them (at least until we get the right Ξ types), and we need proof for heterogeneous equality tests for these singleton families, so we can compare two singleton and get at the level of knowledge, when they become witnesses to the same level values. Then, as long as your GADTs carry single witnesses for any existential factors, you can evenly test equality, ensuring that positive test results using a singleton test give a bonus of unifying types for other tests.