I've had to interface two libraries where metadata is represented as a type parameter in one and as a record field in the other. I wrote an adaptor using a GADT. Here's a distilled version:
{-# LANGUAGE GADTs #-}
newtype TFId a = MkId a
data TFDup a = MkDup !a !a
data GADT tf where
ConstructorId :: GADT TFId
ConstructorDup :: GADT TFDup
main = do
f ConstructorId
f ConstructorDup
f :: GADT tf -> IO ()
f = _
This works. (May not be perfect; comments welcome, but that's not the question.)
It took me some time to get to this working state. My initial intuition was to use a type family for TFId, figuring: “GADT has kind (* -> *) -> *; in ConstructorDup TFDup has kind * -> *; so for ConstructorId I can use the following * -> * type family:”
{-# LANGUAGE TypeFamilies #-}
type family TFId a where TFId a = a
The type constructor does have the same kind * -> *, but GHC apparently won't have it in the same place:
error: …
- The type family ‘TFId’ should have 1 argument, but has been given none
- In the definition of data constructor ‘ConstructorId’ In the data type declaration for ‘GADT’
Well, if it says so…
I'm no sure I understand why it would make such a difference. No using type family stems without applying them? What's going on? Any other (better) way to do?
Injectivity.
here
F Int ~ F Char. However,will never cause
G Int ~ G Char. These are guaranteed to be distinct types.In universal quantifications like
fis allowed to beG(injective) but not allowed to beF(not injective).This is to make inference work.
foo (... :: G Int)can be inferred to have typeInt.foo (... :: F Int)is equivalent tofoo (... :: Bool)which may have typeInt, or typeChar-- it's an ambiguous type.Also consider
foo True. We can't expect GHC to choosef ~ F, a ~ Int (or Char)for us. This would involve looking at all type families and see ifBoolcan be produced by any on them -- essentially, we would need to invert all the type families. Even if this were feasible, it would generate a huge amount of possible solutions, so it would be ambiguous.