Home > Enterprise >  How to obtain a Data.Data.Constr etc. from a Type Representation?
How to obtain a Data.Data.Constr etc. from a Type Representation?

Time:01-20

I'm currently writing a minimalistic Haskell persistence framework that uses Data.Data Generics to provide persistence operations for data types in record syntax (I call them Entities here). This works quite well overall (see code repo here: https://github.com/thma/generic-persistence), I've got just one ugly spot left.

Currently my function for looking up an entity by primary key has the following signature:

retrieveEntityById 
  :: forall a conn id. (Data a, IConnection conn, Show id) 
  => conn -> TypeInfo -> id -> IO a

This function takes an HDBC database connection, A TypeInfo object and the primary key value id.

The TypeInfo contains data describing the type a. This Info will be used to generate and perform a select statement for primary key lookup and contruct an instance of type a from the HDBC result row.

TypeInfo contains Data.Data.Constr and a description of all constructor fields that are obtained with the following function:

-- | A function that returns a list of FieldInfos representing the 
--   name, constructor and type of each field in a data type.
fieldInfo :: (Data a) => a -> [FieldInfo]
fieldInfo x = zipWith3 FieldInfo names constrs types
  where
    constructor = toConstr x
    candidates = constrFields constructor
    constrs = gmapQ toConstr x
    types = gmapQ typeOf x
    names =
      if length candidates == length constrs
        then map Just candidates
        else replicate (length constrs) Nothing

Deriving this kind of information works great when having an actual Data a value at hand (for example in my function for updating an existing entity).

But in the retrieveEntityById case this is not possible, as the object has yet to loaded from the DB. That's why I have to call the function with an extra TypeInfo parameter that I create from a sample entity.

I would like to get rid of this extra parameter, to have a function signature like:

retrieveEntityById 
  :: forall a conn id. (Data a, IConnection conn, Show id) 
  => conn -> id -> IO a

I tried several things like using a Proxy or a TypeRep, but I did not manage to derive my TypeInfo data from them.

Any hints and ideas are most welcome!

CodePudding user response:

You can access metadata from Data a constraints without a value x :: a:

  • dataTypeOf (undefined :: a) is a representation of the type a. It takes an undefined argument because Data is an old interface from a time when undefined was considered more acceptable.

  • dataTypeConstrs extracts the list of constructors from that representation.

  • As you already noted in your code, although there is constrFields to get field names, there isn't an obvious way to get the arity of a non-record constructor. A solution to count the fields of a constructor is to use gunfold with the Const Int functor.

Although your current approach seems specialized to types with a single constructor, this is not a fundamental limitation. For more than one constructor, you could store the constructor name in its persistent encoding, and on decoding, it can be looked up in the list from dataTypeConstrs.

  • Related