{-# LANGUAGE AllowAmbiguousTypes #-} module Database.GP.TypeInfo ( TypeInfo, fieldNames, fieldTypes, constructorName, typeInfo, HasConstructor (..), HasSelectors (..), ) where import Data.Kind (Type) import GHC.Generics import Type.Reflection (SomeTypeRep (..), Typeable, typeRep) -- | A data type holding meta-data about a type. -- The Phantom type parameter `a` ensures type safety for reflective functions -- that use this type to create type instances. data TypeInfo a = TypeInfo { constructorName :: String, fieldNames :: [String], fieldTypes :: [SomeTypeRep] } -- | this function is a smart constructor for TypeInfo objects. -- It takes a value of type `a` and returns a `TypeInfo a` object. -- If the type has no named fields, an error is thrown. -- If the type has more than one constructor, an error is thrown. typeInfo :: forall a. (HasConstructor (Rep a), HasSelectors (Rep a), Generic a) => TypeInfo a typeInfo = TypeInfo { constructorName = gConstrName (undefined :: a), fieldNames = map fst (gSelectors (undefined :: a)), fieldTypes = map snd (gSelectors (undefined :: a)) } -- Generic implementations gConstrName :: (HasConstructor (Rep a), Generic a) => a -> String gConstrName = genericConstrName . from class HasConstructor (f :: Type -> Type) where genericConstrName :: f x -> String instance HasConstructor f => HasConstructor (D1 c f) where genericConstrName (M1 x) = genericConstrName x -- instance (HasConstructor x, HasConstructor y) => HasConstructor (x :+: y) where -- genericConstrName (L1 l) = genericConstrName l -- genericConstrName (R1 r) = genericConstrName r instance Constructor c => HasConstructor (C1 c f) where genericConstrName = conName -- field names & types gSelectors :: forall a. (HasSelectors (Rep a)) => a -> [(String, SomeTypeRep)] gSelectors _x = selectors @(Rep a) class HasSelectors rep where selectors :: [(String, SomeTypeRep)] instance HasSelectors f => HasSelectors (M1 D x f) where selectors = selectors @f instance HasSelectors f => HasSelectors (M1 C x f) where selectors = selectors @f instance (Selector s, Typeable t) => HasSelectors (M1 S s (K1 R t)) where selectors = [(selName (undefined :: M1 S s (K1 R t) ()), SomeTypeRep (typeRep @t))] instance (HasSelectors a, HasSelectors b) => HasSelectors (a :*: b) where selectors = selectors @a ++ selectors @b -- instance HasSelectors U1 where -- selectors = []