编写用于 GHC.Generics 的类型级函数

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

我正在尝试使用 GHC.Generics 编写一个通用函数,它将 return 一个值中使用的所有数据类型名称。

这是我目前的情况:

{-# 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)

唯一缺少的部分是 M1 i c a 实例定义。

如何调用 datatypeName 来获取类型的名称?

我正在关注 Stephan Diehl 关于泛型的 "What I Wish I Knew..." 博客 post [1]

[1] http://dev.stephendiehl.com/hask/#generic

第一个提示,如果您还不知道:您可以在 GHCi 中通过 :kind! 查看类型 Rep-s。例如:

> :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])))

至于实际问题,对于当前作业datatypeName不适用,相反我们可以使用typeOfData.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)  

示例:

> data Foo = Foo Int Bool () deriving (Generic)
> names $ Foo 0 True ()
[Int,Bool,()]

请注意,虽然此实现不是递归的,但它只查看最顶层构造函数的字段。

> names [0, 1]
[Integer, [Integer]]

递归版本将涉及更多的机制,因为某些类型的表示很容易导致天真的实现中的无限循环,因此我们必须跟踪访问过的字段。