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?
newtype
s are erased during code generation - the runtime representation of a newtype is the same as the wrapped type and it's free to coerce in either direction - but they are not expanded during type checking liketype
s are.newtype
s are apart from any other type; type synonyms/families are not. – Benjamin Hodgsondata family
though, if you wanted. They're like type families but injective. – luquif
does. Will give it a try. – JB.