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 typea
. It takes anundefined
argument becauseData
is an old interface from a time whenundefined
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 usegunfold
with theConst 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
.