列出所有记录字段
这是非常有可能的,并且确实是通过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
和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 中无法区分。无法检查magic recordId
.
2019 年更新:使用 GHC 8.6.5 和 typed TypeRep
s 提取选择器。我们通过摆脱代理以支持类型应用程序来使解决方案现代化一点。
{-# 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)
.