Haskell中如何在ADT内定义非Int类型的披萨尺寸常量?
解决Haskell披萨尺寸ADT关联自定义数值的方案
下面是几种符合需求的实现方式,可根据你的场景选择:
方案1:保留原枚举ADT,添加映射函数
这种方式最直接,保留你原来的枚举定义,通过单独的函数把每个尺寸映射到自定义的Inch类型,既保留模式匹配的便利性,又能关联语义明确的数值:
-- 自定义长度类型,明确语义为英寸 newtype Inch = Inch Double deriving (Show, Eq) -- 原有的尺寸枚举ADT data Size = Small | Medium | Large | ExtraLarge deriving (Show, Eq) -- 映射函数:将尺寸转换为对应英寸值 sizeToInch :: Size -> Inch sizeToInch Small = Inch 9.5 sizeToInch Medium = Inch 12.0 sizeToInch Large = Inch 14.0 sizeToInch ExtraLarge = Inch 16.0
优点
- 完全保留原ADT的结构,模式匹配逻辑无需修改
- 自定义
Inch类型避免了通用数值(如Int)的语义模糊问题 - 实现简单,适合玩具程序快速迭代
缺点
- 添加新尺寸时,需要同时修改ADT定义和映射函数,容易遗漏(可通过编写测试用例规避)
方案2:智能构造器+封装尺寸值
如果想严格限制只能创建合法的披萨尺寸,避免用户构造无效值,可以用智能构造器封装内部实现,只暴露预定义的合法尺寸:
newtype Inch = Inch Double deriving (Show, Eq) -- 隐藏内部构造器,仅通过预定义变量创建实例 data Size = Size Inch deriving (Show) -- 暴露的合法尺寸 small, medium, large, extraLarge :: Size small = Size (Inch 9.5) medium = Size (Inch 12.0) large = Size (Inch 14.0) extraLarge = Size (Inch 16.0) -- 可选:添加判断尺寸类型的辅助函数 isSmall :: Size -> Bool isSmall (Size (Inch 9.5)) = True isSmall _ = False
优点
- 彻底杜绝无效尺寸的创建,保证数据合法性
- 尺寸和数值绑定紧密,无需额外映射函数
缺点
- 原有的枚举模式匹配需要调整(比如要匹配
Size inch而非直接的Small) - 判断具体尺寸需要额外编写辅助函数
方案3:类型类实现尺寸关联
如果以后需要给其他类型(比如饼底厚度、配料分量)也添加尺寸属性,可以用类型类实现复用:
newtype Inch = Inch Double deriving (Show, Eq) data Size = Small | Medium | Large | ExtraLarge deriving (Show, Eq, Enum, Bounded) -- 定义通用的尺寸类型类 class HasSize a where getSize :: a -> Inch -- 让Size实例化类型类 instance HasSize Size where getSize Small = Inch 9.5 getSize Medium = Inch 12.0 getSize Large = Inch 14.0 getSize ExtraLarge = Inch 16.0
优点
- 扩展性强,其他需要尺寸属性的类型可直接实例化
HasSize - 保留原ADT的枚举结构,模式匹配不受影响
缺点
- 比方案1稍复杂,玩具程序可能没必要用到这么多抽象
内容的提问来源于stack exchange,提问作者Jir
相关产品推荐
相关产品推荐

