Haskell introspecting a record's field names and types
-
10-04-2021 - |
Question
Based on a recent exchange, I've been convinced to use Template Haskell to generate some code to ensure compile-time type safety.
I need to introspect record field names and types. I understand I can get field names by using constrFields . toConstr :: Data a => a -> [String]
. But I need more than the field names, I need to know their type. For example, I need to know the names of fields that are of type Bool
.
How do I construct a function f :: a -> [(String, xx)]
where a
is the record, String
is the field name and xx
is the field type?
La solution
The type should be available, along with everything else, in the Info
value provided by reify
. Specifically, you should get a TyConI
, which contains a Dec
value, from which you can get the list of Con
values specifying the constructors. A record type should then use RecC
, which will give you a list of fields described by a tuple containing the field name, whether the field is strict, and the type.
Where you go from there depends on what you want to do with all this.
Edit: For the sake of actually demonstrating the above, here's a really terrible quick and dirty function that finds record fields:
import Language.Haskell.TH
test :: Name -> Q Exp
test n = do rfs <- fmap getRecordFields $ reify n
litE . stringL $ show rfs
getRecordFields :: Info -> [(String, [(String, String)])]
getRecordFields (TyConI (DataD _ _ _ cons _)) = concatMap getRF' cons
getRecordFields _ = []
getRF' :: Con -> [(String, [(String, String)])]
getRF' (RecC name fields) = [(nameBase name, map getFieldInfo fields)]
getRF' _ = []
getFieldInfo :: (Name, Strict, Type) -> (String, String)
getFieldInfo (name, _, ty) = (nameBase name, show ty)
Importing that in another module, we can use it like so:
data Foo = Foo { foo1 :: Int, foo2 :: Bool }
foo = $(test ''Foo)
Loading that in GHCi, the value in foo
is [("Foo",[("foo1","ConT GHC.Types.Int"),("foo2","ConT GHC.Types.Bool")])]
.
Does that give you the rough idea?