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

2024-05-05

我知道对于派生 Data.Data 的数据类型,constrFields http://hackage.haskell.org/package/base-4.7.0.2/docs/Data-Data.html#v:constrFields给出字段名称的列表.看看 GHC.Generics 文档,我认为同样的事情应该是可能的Generic以及。 (但不幸的是我自己无法弄清楚如何做到这一点)。

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

列出所有记录字段

...在 Haskell 程序中。我知道aeson http://hackage.haskell.org/package/aeson能够自动推断派生的任何记录数据类型的 JSON 表示形式Generic,但阅读其源代码只能证实我在这里一无所知。据我猜测,aeson 必须能够获取所有字段名称(如Strings or ByteStrings)来自记录数据类型,以及它们的类型(具有类似的类型)TypeRep在 Data.Typeable 中,或一个实例Eq: 任何可以用来做的事情case块模式匹配就可以了)。

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

检查记录选择器

要获取其所属的记录数据类型,记录字段名称(如String), etc.

例如,给定

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

一个功能magic那就像

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

这些可能与deriving Generic,或者我必须求助于 Template Haskell 吗?


列出所有记录字段

这个很有可能,而且确实是通过递归结构来完成的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)]

这里最不明显的部分是selName and Selector:这个类可以在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_0Record and Main.S1_0_1Record。我们只能通过从Rep使用类或类型族进行类型,因为 GHC 不导出它们。反正,selName为我们提供选择器名称M1节点与s选择器标签(它有一个更通用的类型t s f a -> String但这与我们无关)。

也可以处理多个构造函数,并且有selectors return [[(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.


2019年更新:使用 GHC 8.6.5 提取选择器并键入TypeReps。我们通过摆脱代理而转而使用类型应用程序,对解决方案进行了一些现代化改造。

{-# language
  AllowAmbiguousTypes,
  DeriveGeneric,
  FlexibleContexts,
  FlexibleInstances,
  RankNTypes,
  TypeApplications,
  TypeInType
  #-}

import Type.Reflection
import GHC.Generics

class Selectors rep where
  selectors :: [(String, SomeTypeRep)]

instance Selectors f => Selectors (M1 D x f) where
  selectors = selectors @f

instance Selectors f => Selectors (M1 C x f) where
  selectors = selectors @f

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

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

instance Selectors U1 where
  selectors = []

现在用法变成了selectors @(Rep MyType).

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

是否可以列出派生 Generic 的记录数据类型中字段的名称和类型? 的相关文章

随机推荐