如何创建含受限一元数据构造器的Haskell和类型
Haskell 街道门店归属的数据类型建模方案
你想实现的「通过类型声明直接固化门店和街道的归属关系」完全可以做到,不需要额外写运行时的映射查询逻辑,甚至能在编译阶段就拦截归属错误的非法赋值,以下是常规的实现思路:
最直接的一元构造器实现
你一开始写的TownClothesStores类型骨架完全可用,不需要额外定义把5家门店平级铺开的ClothesStores类型,直接给每个一元构造器绑定对应街道专属的门店枚举即可,归属关系直接写在类型定义里:
-- 按街道分别定义专属门店枚举,从类型层面绑定归属 data GrantStreetStore = Treschic | Muoychic -- Grant街共2家 data LeeStreetStore = Verychic -- Lee街共1家 data LincolnStreetStore = Sehrchic | Mycketbra -- Lincoln街共2家 -- 外层类型用一元构造器,强关联门店与所属街道 data TownClothesStores = Grant GrantStreetStore | Lee LeeStreetStore | Lincoln LincolnStreetStore
这种写法下,只要匹配到Grant x的结构,x必然是Grant街的两家门店之一,不可能出现把其他街道的门店塞进Grant构造器的情况,编译期直接报错。要查询某条街的对应门店,不需要写任何运行时逻辑,直接看对应街道的门店类型构造器就能拿到全量列表,完全是类型声明自带的信息。
对比你提到的平级ClothesStores写法,那种实现把所有门店摊在同一个类型里,门店和街道的归属完全脱节,必须额外手写getStreet :: ClothesStores -> Street这类映射函数维护关系,一旦新增门店忘了更新映射就会出bug,属于把类型系统就能兜住的逻辑扔到运行时处理,没有必要。
更灵活的幻影类型实现
如果你不想为每条街道单独定义门店类型,想复用统一的门店枚举,同时保留类型层面的归属标记,可以用幻影类型方案:
-- 定义空的街道标记类型,仅用于类型层面的归属标记,没有实际运行时值 data GrantSt data LeeSt data LincolnSt -- 门店类型带幻影类型参数,标记所属街道 data ClothesStore street = Treschic | Muoychic | Verychic | Sehrchic | Mycketbra -- 通过显式类型签名把门店和所属街道绑定,禁止非法赋值 treschic :: ClothesStore GrantSt treschic = Treschic muoychic :: ClothesStore GrantSt muoychic = Muoychic verychic :: ClothesStore LeeSt verychic = Verychic sehrchic :: ClothesStore LincolnSt sehrchic = Sehrchic mycketbra :: ClothesStore LincolnSt mycketbra = Mycketbra
这种写法支持编写针对特定街道门店的专属逻辑,比如定义grantStreetPromotion :: ClothesStore GrantSt -> Discount函数时,其他街道的门店根本传不进来,编译期直接拦截非法参数,不需要任何运行时判断。
建模实践参考
- 不要用
String或Int这类弱类型表示门店:这类写法没有任何编译期校验,传错门店名、错编号都不会触发报错,后期维护全靠人肉核对,隐患极多。 - 固定的关联关系优先在类型层面固化:像这类不会动态变化的归属、映射关系,用类型系统约束是Haskell的常规操作,既省掉了手写维护映射逻辑的成本,还能把大量bug拦在编译阶段。
- 如果需要运行时获取某条街的全量门店列表,给对应街道的门店类型派生
Bounded和Enum类型类即可,直接用[minBound .. maxBound]就能拿到全量门店,连手写列表的工作都能省掉:
-- 派生后自动支持枚举全量值 data GrantStreetStore = Treschic | Muoychic deriving (Bounded, Enum, Show) -- 计算 [minBound .. maxBound] :: [GrantStreetStore] 直接得到 [Treschic, Muoychic]
内容的提问来源于stack exchange,提问作者147pm
相关产品推荐
相关产品推荐

