如何通过Generics静态分析类型结构,无需具体值实现FromRow类派生?
方案解答
1. 基于 Generic 的纯类型层面分析方案
完全可以通过 Generic 仅在类型层面分析结构,不需要构造对应类型的实际值。
你之前混淆了 Generic 的两层能力:from方法确实是运行时把实际值转为泛型表示,但 Generic 核心是把类型结构映射到Rep这个类型层面的抽象结构,你完全可以通过定义针对Rep结构的类型类实例,纯静态地遍历类型的字段、构造器等信息,完全不需要用到实际值。
具体实现示例如下:
{-# LANGUAGE TypeApplications #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE UndecidableInstances #-} -- 辅助类:针对Generic的Rep结构实现rowrep的生成逻辑 class GFromRow f where gRowrep :: Proxy f -> [Maybe NativeType] -- 处理泛型表示中的元数据包裹层(类型元数据、构造器元数据、字段元数据) instance GFromRow f => GFromRow (M1 i c f) where gRowrep _ = gRowrep (Proxy :: Proxy f) -- 处理乘积类型的组合(多个字段拼接) instance (GFromRow a, GFromRow b) => GFromRow (a :*: b) where gRowrep _ = gRowrep (Proxy @a) ++ gRowrep (Proxy @b) -- 辅助类:获取单个类型对应的NativeType class HasNativeType t where getNativeType :: Proxy t -> Maybe NativeType -- 基础类型的HasNativeType实例,根据你的NativeType定义填充即可 instance HasNativeType String where getNativeType _ = Just {- 字符串对应的NativeType值 -} instance HasNativeType Int where getNativeType _ = Just {- 整型对应的NativeType值 -} instance HasNativeType Bool where getNativeType _ = Just {- 布尔型对应的NativeType值 -} -- 处理泛型表示中的字段值包裹层 instance HasNativeType t => GFromRow (K1 i t) where gRowrep _ = [getNativeType (Proxy @t)]
之后给FromRow类添加默认实现:
{-# LANGUAGE DefaultSignatures #-} class FromRow a where rowrep :: Proxy a -> [Maybe NativeType] fromRow :: Statement -> IO a -- 支持Generic的类型自动获得默认实现 default rowrep :: (Generic a, GFromRow (Rep a)) => Proxy a -> [Maybe NativeType] rowrep _ = gRowrep (Proxy @(Rep a)) -- fromRow也可以按照同样的泛型遍历逻辑补全默认实现,不需要提前构造a的值 default fromRow :: (Generic a, GFromRow (Rep a)) => Statement -> IO a -- 省略fromRow的泛型实现,逻辑和rowrep遍历结构一致,逐字段读取后构造Rep再转成实际类型即可
完成上述定义后,你只要给数据类型派生Generic,不需要写任何额外代码就能自动获得FromRow实例,调用rowrep (Proxy @User)完全不需要构造User类型的任何值,就能得到对应的字段类型列表。
2. Template Haskell 方案
如果不想用Generic,用Template Haskell也完全可以满足需求。你可以写TH函数遍历数据类型的定义,直接为每个数据类型生成对应的FromRow实例。相比Generic方案,TH生成的实例编译速度更快,报错信息更直观,但需要额外维护TH模板代码,使用时需要导入模板并显式调用生成实例的宏。
内容的提问来源于stack exchange,提问作者user2847643
相关产品推荐
相关产品推荐

