Min*_*oru 12 generics haskell type-systems aeson
我知道对于派生Data.Data的数据类型,constrFields
给出了字段名称列表.看一下GHC.Generics文档,我认为同样应该也是可能的Generic
.(但很难自己弄清楚如何去做).
更具体地说,我正在寻找两件事:
......在Haskell程序中.我知道aeson能够自动推断出任何记录数据类型的JSON表示Generic
,但是阅读它的源代码只能证实我在这里一无所知.根据我的猜测,aeson必须能够从记录数据类型获取所有字段名称(如String
s或ByteString
s),以及它们的类型(具有类似于TypeRep
Data.Typeable 的类型,或者实例Eq
:任何可以用于case
块模式匹配的东西都可以.
我隐约假定创建类和实例M1
,:*:
等等.是这样的,但我不能使它的类型检查.
获取它所属的记录数据类型,记录字段名称(as String
)等.
例如,给定
data Record = Record
{ recordId :: Int32
, recordName :: ByteString
} deriving Generic
Run Code Online (Sandbox Code Playgroud)
功能magic
就像
typeOf (Record {}) == typeOf (magic recordId)
Run Code Online (Sandbox Code Playgroud)
这些是可能的deriving Generic
,还是我必须诉诸模板Haskell?
And*_*ács 17
这个非常有可能,它确实是通过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 _ = []
Run Code Online (Sandbox Code Playgroud)
现在我们可以:
selectors (Proxy :: Proxy (Rep Record))
-- [("recordId",Int32),("recordName",ByteString)]
Run Code Online (Sandbox Code Playgroud)
这里最不明显的部分是:selName
和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)))
Run Code Online (Sandbox Code Playgroud)
和选择器类型是Main.S1_0_0Record
和Main.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)
Run Code Online (Sandbox Code Playgroud)
或者静态地:
type family Arg f where
Arg (a -> b) = a
Run Code Online (Sandbox Code Playgroud)
但是,如果没有TH,我们无法知道函数是合法的选择器还是只是具有正确类型的函数; 他们在Haskell中难以区分.无法检查名称"recordId" magic recordId
.