Haskell成员可独立使用的和类型实现:PDF值类型建模方案问询
解决方案
方案1:仅对约束字段做newtype包装(最常用的轻量方案)
不需要拆分所有子类型为独立类型,只需要把有特殊约束的字典键(Name)单独做成newtype,保留Value的ADT结构即可,既满足类型约束,又不会引入过多冗余:
-- 单独定义Name类型,保证字典键只能是该类型 newtype Name = Name String instance Show Name where show (Name name) = "/" ++ name data Value = Text String | NameValue Name | Dictionary [(Name, Value)] -- 键强制为Name类型,不可能传入其他值 instance Show Value where show (Text text) = "(" ++ text ++ ")" show (NameValue name) = show name show (Dictionary entries) = "<<" ++ unlines (showEntry <$> entries) ++ ">>" where showEntry (key, value) = show key ++ " " ++ show value
构造示例:
Dictionary [(Name "foo", Text "bar"), (Name "test", NameValue (Name "baz"))]
如果要进一步消除NameValue的重复,可以加一个智能构造函数:
name :: String -> Value name = NameValue . Name -- 构造简化为: Dictionary [(Name "foo", Text "bar"), (Name "test", name "baz")]
方案2:用模式同义词消除冗余包装(适合需要严格拆分类型的场景)
如果你确实需要把Text、Name、Dictionary都拆分为独立类型(比如要给不同类型实现不同的类型类实例),可以开启PatternSynonyms扩展来消除两层构造器的冗余:
{-# LANGUAGE PatternSynonyms #-} newtype Text = Text String newtype Name = Name String newtype Dictionary = Dictionary [(Name, Value)] data Value = TextValue Text | NameValue Name | DictionaryValue Dictionary -- 定义模式同义词,直接用简洁的语法构造/匹配Value pattern T :: String -> Value pattern T s = TextValue (Text s) pattern N :: String -> Value pattern N s = NameValue (Name s) pattern D :: [(Name, Value)] -> Value pattern D entries = DictionaryValue (Dictionary entries) -- 构造完全没有冗余: D [(Name "foo", T "bar"), (Name "test", N "baz")]
匹配的时候也可以直接用T s、N s、D entries,和普通ADT构造器完全一致。
方案3:GADT+DataKinds(最高类型安全度)
如果需要更精细的类型控制,可以用GADT给不同值类型打上类型标签,强制约束不同场景的合法类型:
{-# LANGUAGE GADTs, DataKinds #-} -- 值类型标签 data ValueType = TextT | NameT | DictT -- 带类型标签的Value,不同构造器对应不同标签 data Value (t :: ValueType) where Text :: String -> Value TextT Name :: String -> Value NameT -- 字典键强制为Name类型的Value Dictionary :: [(Value NameT, Value a)] -> Value DictT -- 用于存储任意类型Value的包装类型 data AnyValue where AnyValue :: Value t -> AnyValue instance Show (Value t) where show (Text text) = "(" ++ text ++ ")" show (Name name) = "/" ++ name show (Dictionary entries) = "<<" ++ unlines (showEntry <$> entries) ++ ">>" where showEntry (key, value) = show key ++ " " ++ show value
构造示例完全无冗余,且不可能构造出非法的字典键:
Dictionary [(Name "foo", Text "bar"), (Name "test", Name "baz")]
业界常规方案
这类嵌套结构(JSON、AST、自定义格式值类型)的常规设计原则是:
- 对有特殊约束的字段优先用newtype做类型区分,不要为了少写代码用通用类型(比如String)代替,避免运行时错误
- 冗余包装的问题可以通过智能构造函数、模式同义词这类语法糖解决,不需要为了简化构造牺牲类型安全
- 如果需要极致的类型控制,GADT是这类场景的标准解决方案,在各类编译器、格式解析库中应用非常广泛
内容的提问来源于stack exchange,提问作者Jules
相关产品推荐
相关产品推荐

