Reputation: 52029
I'm trying to use GHC.Generics
to write a generic function which will return all of the data type names used in a value.
Here is what I have so far:
{-# LANGUAGE DefaultSignatures, DeriveGeneric, TypeOperators, FlexibleContexts #-}
{-# LANGUAGE MonomorphismRestriction #-}
module Lib4 where
import GHC.Generics
class Names f where
names' :: f a -> [String]
instance Names U1 where
names' _ = []
instance (Names a, Names b) => Names (a :+: b) where
names' (L1 x) = names' x
names' (R1 x) = names' x
instance (Names a, Names b) => Names (a :*: b) where
names' (a :*: b) = names' a ++ names' b
instance Names (M1 i c a) where
names' (M1 x) = ... -- use datatypeName here?
names x = names' (from x)
The only part that is missing is in the M1 i c a
instance definition.
How do I invoke datatypeName
to get at the name of the type?
I'm following Stephan Diehl's "What I Wish I Knew..." blog post on Generics [1]
[1] http://dev.stephendiehl.com/hask/#generic
Upvotes: 3
Views: 177
Reputation: 30113
First tip, if you don't know this already: you can view type Rep
-s by :kind!
in GHCi. For example:
> :kind! Rep [Int]
Rep [Int] :: * -> *
= D1
GHC.Generics.D1[]
(C1 GHC.Generics.C1_0[] U1
:+: C1
GHC.Generics.C1_1[]
(S1 NoSelector (Rec0 Int) :*: S1 NoSelector (Rec0 [Int])))
As to the actual question, for the current job datatypeName
isn't applicable, instead we can recover the types of fields using typeOf
from Data.Typeable
.
{-# LANGUAGE MonomorphismRestriction #-}
import GHC.Generics
import Data.Typeable
class Names f where
names' :: f a -> [TypeRep]
instance Names U1 where
names' _ = []
instance (Names a, Names b) => Names (a :+: b) where
names' (L1 x) = names' x
names' (R1 x) = names' x
instance (Names a, Names b) => Names (a :*: b) where
names' (a :*: b) = names' a ++ names' b
instance Names f => Names (M1 i c f) where
names' (M1 fa) = names' fa
instance (Typeable t) => Names (Rec0 t) where
names' (K1 x) = [typeOf x]
names x = names' (from x)
Example:
> data Foo = Foo Int Bool () deriving (Generic)
> names $ Foo 0 True ()
[Int,Bool,()]
Note though that this implementation is not recursive, it just looks at the fields of the topmost constructor.
> names [0, 1]
[Integer, [Integer]]
A recursive version would involve more machinery, since some types have representations that easily lead to infinite loops in naive implementations, and thus we'd have to keep track of visited fields.
Upvotes: 3