3

I was wondering if given a constructor, such as:

data UserType = User
  { username :: String
  , password :: String
  } -- deriving whatever necessary

What the easiest way is for me to get something on the lines of [("username", String), ("password", String)], short of just manually writing it. Now for this specific example it is fine to just write it but for a complex database model with lots of different fields it would be pretty annoying.

So far I have looked through Typeable and Data but so far the closest thing I have found is:

user = User "admin" "pass"
constrFields (toConstr user)

But that doesn't tell me the types, it just returns ["username", "password"] and it also requires that I create an instance of User.

2
  • 3
    What is [("username", String), ("password", String)]? A type level list of pairs? Did you mean [("username", "String"), ("password", "String")] or something similar? Commented Mar 8, 2016 at 21:59
  • @Zeta I mean a string would work OK, but I phrased it that way because it could also be a more meaningful type. Such as something similar to Data.Data.Constr which can be inspected with other functions from Data.Data. Commented Mar 8, 2016 at 23:25

1 Answer 1

4

I just knocked out a function using Data.Typeable that lets you turn a constructor into a list of the TypeReps of its arguments. In conjunction with the constrFields you found you can zip them together to get your desired result:

{-# LANGUAGE DeriveDataTypeable #-}

module Foo where
import Data.Typeable
import Data.Typeable.Internal(funTc)

getConsArguments c = go (typeOf c)
   where go x = let (con, rest) = splitTyConApp x    
                in if con == funTc         
                   then case rest of (c:cs:[]) -> c : go cs
                                     _ -> error "arrows always take two arguments"
                   else []                      

given data Foo = Foo {a :: String, b :: Int} deriving Typeable, we get

*> getConsArguments Foo
[[Char],Int]

As one would hope.


On how to get the field names without using a populated data type value itself, here is a solution:

constrFields . head . dataTypeConstrs $ dataTypeOf (undefined :: Foo)
Sign up to request clarification or add additional context in comments.

4 Comments

It should be noted that Typeable is likely to change substantially in GHC 8.2, and this may well need to be changed then.
Thank you! Would you mind showing me how you can also get the names of the fields. I know my example got those but I would love to be able to do it without creating an instance. Also how does this even work? When I do :t it says it is looking for a Typeable a but I thought the constructor was a function and the instance was the Typeable a, yet it lets me pass in the constructor.
@semicolon edited to show that portion of the solution. As to how it works, we are using Typeable to get the type of the constructor function then inducting on it to turn it into a list of its arguments.
@sclv Alright that makes a lot of sense thanks. Also I realized that you don't actually need to derive Typeable, as it is the fact that Foo is a function and functions derive Typeable that makes the whole thing work, which makes a lot of sense, as I was still pretty confused about that whole thing. And the undefined cheat is pretty sweet.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.