ErikR
ErikR

Reputation: 52029

writing a type-level function for use with GHC.Generics

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

Answers (1)

András Kovács
András Kovács

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

Related Questions