是否可以列出导出Generic的记录数据类型中的字段名称和类型?

时间:2015-01-07 08:46:26

标签: haskell generics type-systems aeson

我知道对于派生Data.Data的数据类型,constrFields给出了字段名称列表。看一下GHC.Generics文档,我认为Generic也应该可以这样做。 (但很难自己弄明白怎么做)。

更具体地说,我正在寻找两件事:

列出所有记录字段

...在Haskell程序中。我知道aeson能够自动推断派生Generic的任何记录数据类型的JSON表示,但阅读其源代码只能证实我在这里一无所知。根据我的猜测,aeson必须能够从记录数据类型获取所有字段名称(如String s或ByteString s),以及它们的类型(具有类似的类型) Data.Typeable中的TypeRepEq的实例:任何可用于case块模式匹配的内容都可以。)

我模糊地假设为M1:*:等创建一个类和实例是合适的方式,但我无法进入类型检查器。

检查记录选择器

要获取它所属的记录数据类型,记录字段名称(如String)等。

例如,给定

data Record = Record
    { recordId :: Int32
    , recordName :: ByteString
    } deriving Generic

功能magic就像

typeOf (Record {}) == typeOf (magic recordId)

这些是deriving Generic可能的,还是我必须求助于模板Haskell?

1 个答案:

答案 0 :(得分:15)

列出所有记录字段

这个很有可能,而且确实是通过使用类来递归Rep的结构来完成的。下面的解决方案适用于单构造函数类型,并为没有选择器的字段返回空字符串名称:

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE ScopedTypeVariables #-}

import Data.ByteString (ByteString)
import Data.Data
import Data.Int
import Data.Proxy
import GHC.Generics
import qualified Data.ByteString as B

data Record = Record { recordId :: Int32, recordName :: ByteString }
  deriving (Generic)

class Selectors rep where
  selectors :: Proxy rep -> [(String, TypeRep)]

instance Selectors f => Selectors (M1 D x f) where
  selectors _ = selectors (Proxy :: Proxy f)

instance Selectors f => Selectors (M1 C x f) where
  selectors _ = selectors (Proxy :: Proxy f)

instance (Selector s, Typeable t) => Selectors (M1 S s (K1 R t)) where
  selectors _ =
    [ ( selName (undefined :: M1 S s (K1 R t) ()) , typeOf (undefined :: t) ) ]

instance (Selectors a, Selectors b) => Selectors (a :*: b) where
  selectors _ = selectors (Proxy :: Proxy a) ++ selectors (Proxy :: Proxy b)

instance Selectors U1 where
  selectors _ = []

现在我们可以:

selectors (Proxy :: Proxy (Rep Record))
-- [("recordId",Int32),("recordName",ByteString)]

此处最不明显的部分是selNameSelector:此类可以在GHC.Generics中找到,它允许我们从生成的选择器类型中提取选择器名称。在Record的情况下,表示是

:kind! Rep Record
Rep Record :: * -> *
= D1
    Main.D1Record
    (C1
       Main.C1_0Record
       (S1 Main.S1_0_0Record (Rec0 Int32)
        :*: S1 Main.S1_0_1Record (Rec0 ByteString)))

,选择器类型为Main.S1_0_0RecordMain.S1_0_1Record。我们只能通过使用类或类型系列从Rep类型中提取这些类型来访问这些类型,因为GHC不会导出它们。无论如何,selName使用M1选择器标记从任何s节点获取选择器名称(它具有更通用的类型t s f a -> String,但我们并不关心我们这里)。

它还可以处理多个构造函数,并selectors返回[[(String, TypeRep)]]。在这种情况下,我们可能有两个类,一个类似于上面的类,用于从给定的构造函数中提取选择器,另一个类用于收集构造函数的列表。

检查记录选择器

从函数中轻松获取记录类型:

class Magic f where
  magic :: f -> TypeRep

instance Typeable a => Magic (a -> b) where
  magic _ = typeOf (undefined :: a)

或静态地:

type family Arg f where
   Arg (a -> b) = a

但是,如果没有TH,我们无法知道函数是合法的选择器还是只是具有正确类型的函数;他们在Haskell中无法区分。没有办法检查名称" recordId"在magic recordId