如何用generics-sop在编译时获取记录字段名并约束类型?
在generics-sop中编译时约束记录类型并获取字段名
要解决非穷尽匹配警告并确保仅处理记录类型,你可以通过类型级约束在编译阶段强制产品类型的构造器为记录格式,从根源避免运行时错误。
方案1:自定义类型类封装约束
先定义一个继承自IsProductType的类型类,专门约束单构造器的记录类型:
{-# LANGUAGE TypeFamilies, PolyKinds, FlexibleContexts, ScopedTypeVariables #-} import qualified Generics.SOP as SOP import Generics.SOP (IsProductType, Code, DatatypeInfo, ConstructorInfo(Record), FieldInfo, NP(..), hd, datatypeInfo, constructorInfo) import Data.Proxy (Proxy(..)) -- 约束产品类型为单构造器的记录类型 class IsProductType a xs => IsRecordType a xs where getRecordConstructor :: ConstructorInfo xs -- 仅当构造器为Record类型时,才能满足该实例的约束 instance (Code a ~ '[xs], SOP.HasDatatypeInfo a, ConstructorInfo xs ~ Record n (NP FieldInfo xs)) => IsRecordType a xs where getRecordConstructor = case hd (constructorInfo (datatypeInfo (Proxy :: Proxy a))) of Record n fields -> Record n fields -- 类型约束已排除其他构造器,此分支永远不会触发
之后修改你的函数,使用IsRecordType替代原有的IsProductType约束:
f :: forall a xs. IsRecordType a xs => NP FieldInfo xs f = let Record _ fields = getRecordConstructor in fields
方案2:直接在函数签名中添加约束
如果不需要复用约束,可以直接在函数签名里明确构造器的类型:
f :: forall a xs n. ( IsProductType a xs , ConstructorInfo xs ~ Record n (NP FieldInfo xs) -- 强制构造器为记录类型 ) => NP FieldInfo xs f = let Record _ fields = hd (constructorInfo (datatypeInfo (Proxy :: Proxy a))) in fields
核心原理
IsProductType a xs仅保证类型是单构造器的产品类型,但不区分普通构造器(Constructor)、中缀构造器(Infix)和记录构造器(Record)。- 通过添加
ConstructorInfo xs ~ Record n (NP FieldInfo xs)的类型等式约束,我们在编译阶段就固定了构造器的类型:- 编译器会直接拒绝所有非记录格式的产品类型(比如
data Foo = Foo Int String),避免了运行时可能出现的模式匹配失败。 - 由于类型约束已经排除了其他构造器的可能性,编译器不会再发出非穷尽匹配的警告。
- 编译器会直接拒绝所有非记录格式的产品类型(比如
内容的提问来源于stack exchange,提问作者Clinton
相关产品推荐
相关产品推荐

