Writing a type level function for use with GHC.Generics
I am trying to use GHC.Generics
to write a generic function that will return all data type names used in a value.
Here's 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 instance definition M1 i c a
.
How do I call datatypeName
to get the type name?
I follow Stefan Diehl "What I want to know ..." on the blog at Generics [1]
source to share
First tip if you don't already know it: you can look at the Rep
-s type :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 for the actual question, it is datatypeName
not applicable for the current task , instead we can restore the field types 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 that this implementation is not recursive, it just looks at the fields of the topmost constructor.
> names [0, 1]
[Integer, [Integer]]
The recursive version will include more machines, since some types have views that easily lead to infinite loops in naive implementations, and therefore we will need to keep track of the fields visited.
source to share