{-# LANGUAGE ScopedTypeVariables, Safe #-} module Data.Generics.Record ( RecordT, isRecord, recordT, fields, emptyRecord, recordStructure) where import Data.Data import Data.Maybe -- | A phantom type used to parameterize functions based on records. -- This let's us avoid passing @undefined@s or manually creating instances -- all the time. It can only be created for types which are records and -- is used as a token to most of the API's functions. data RecordT a = RecordT -- | Returns @True@ if @a@ is a data type with a single constructor -- and is a record. isRecord :: forall a . Data a => a -> Bool isRecord _ = isJust (recordT :: Maybe (RecordT a)) -- | The smart constructor for @RecordT@s. This -- will return a @RecordT@ if and only if the type is a record. recordT :: forall a. Data a => Maybe (RecordT a) recordT = if isRecord' ra then Just ra else Nothing where ra = (RecordT :: RecordT a) isRecord' :: forall a. Data a => RecordT a -> Bool isRecord' r = length cs == 1 && length (fields r) > 0 where cs = dataTypeConstrs . dataTypeOf $ (undefined :: a) -- | Returns the fields for the record @a@ fields :: forall a. Data a => RecordT a -> [String] fields _ = fs where cs = dataTypeConstrs . dataTypeOf $ (undefined :: a) fs = cs >>= constrFields -- | Return a record where all fields are _|_ emptyRecord :: forall a. Data a => RecordT a -> a emptyRecord _ = fromConstr . head . dataTypeConstrs . dataTypeOf $ (undefined :: a) -- | Return a records structure of as a list of types paired with field names. recordStructure :: forall a. Data a => RecordT a -> [(TypeRep, String)] recordStructure a = zip (gmapQ typeOf (emptyRecord a)) $ fields a