Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell introspecting a record's field names and types

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?

like image 350
Ana Avatar asked Dec 30 '11 23:12

Ana


1 Answers

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?

like image 149
C. A. McCann Avatar answered Oct 17 '22 18:10

C. A. McCann