haskellghc-genericsgenerics-sop

Using `generics-sop` to get type metadata info at compile time


I've been using generics-sop quite successfully so far, but for one use case I'd like to get the names of record fields.

The types I'm working with are product types, and I can use the constraint IsProductType a xs to enforce this.

Doing so I end up with a bunch of code like the following:

import qualified Generics.SOP as SOP
import Generics.SOP (...)

f :: forall a xs. IsProductType a xs => ...
f = ... where
  datatypeInfo :: DatatypeInfo (SOP.Code a)
  datatypeInfo = SOP.datatypeInfo (Proxy :: Proxy a)
  constructorsInfo :: NP ConstructorInfo (Code a)
  constructorsInfo = SOP.constructorInfo datatypeInfo
  constructorInfo :: ConstructorInfo xs
  -- This is safe due to the (IsProductType a xs) constraint, as there is only one constructor
  constructorInfo = hd constructorsInfo 
  fieldsInfo :: NP FieldInfo xs
  fieldsInfo = let Record _ fields = y in fields

But at the last line I get a warning about the Record match not being exhaustive. And it isn't, ConstructorInfo actually has three constructors, and Record is only matching one of them.

So then I realised that my code probably will compile against any product type, even those not in record format. I'd rather not have the incomplete match, but I'd be okay with it if I could somehow put a constraint on a that ensured that it was a record, so the incomplete match will never fail at runtime.

I think the issue is that the function datatypeInfo:

datatypeInfo :: proxy a -> DatatypeInfo (Code a)

Removes type level information, because Code a, being a type level list of lists, no longer has the information about whether a has named record fields or not.

Is there something else I can do using generics-sop to retain that information. I quite like generic-sop, in particular not having to deal with binary trees of sum/product combinations but just dealing with this, so it would be nice if generics-sop exposed the information about whether a field was named or not at compile time as well as runtime.


Solution

  • Answering my own question, I think records-sop solves this issue.