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.
Answering my own question, I think records-sop solves this issue.