--{-# LANGUAGE RankNTypes #-} --{-# LANGUAGE ScopedTypeVariables #-} module Database.GP.TypeInfo ( TypeInfo, typeConstructor, fieldNames, fieldTypes, typeName, typeInfo, typeInfoFromContext, ) where import Data.Data -- | 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 (See module RecordtypeReflection). data TypeInfo a = TypeInfo { typeConstructor :: Constr, fieldNames :: [String], fieldTypes :: [TypeRep] } deriving (Show) -- | 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 :: Data a => a -> TypeInfo a typeInfo x = TypeInfo { typeConstructor = ensureSingleConstructor (dataTypeOf x), fieldNames = fieldNamesOf x, fieldTypes = gmapQ typeOf x } -- | This function ensures that the type of `a` has exactly one constructor. -- If the type has exactly one constructor, the constructor is returned. -- otherwise, an error is thrown. ensureSingleConstructor :: DataType -> Constr ensureSingleConstructor dt = case dataTypeConstrs dt of [cnstr] -> cnstr _ -> error $ "ensureSingleConstructor: Only types with one constructor are supported (" ++ show dt ++ ")" -- | This function creates a TypeInfo object from the context of a function call. -- The Phantom Type parameter `a` is used to convince the compiler that the `TypeInfo a` object really describes type `a`. -- See also https://stackoverflow.com/questions/75171829/how-to-obtain-a-data-data-constr-etc-from-a-type-representation typeInfoFromContext :: forall a. Data a => TypeInfo a typeInfoFromContext = let dt = dataTypeOf (undefined :: a) -- This is the trick to get the type a from the context. constr = ensureSingleConstructor dt evidence = fromConstr constr :: a -- this is evidence for the compiler that we have a value of type a in typeInfo evidence -- | This function returns the (unqualified) type name of `a` from a `TypeInfo a` object. typeName :: TypeInfo a -> String typeName = dataTypeName . constrType . typeConstructor -- | This function returns the list of field names of an entity of type `a`. fieldNamesOf :: (Data a) => a -> [String] fieldNamesOf x = names where constructor = toConstr x candidates = constrFields constructor constrs = gmapQ toConstr x names = if length candidates == length constrs then candidates else error $ "fieldNamesOf: Type " ++ show (typeOf x) ++ " does not have named fields"