Haskell中如何按数据构造器过滤求和类型?附示例
按数据构造器筛选CampingStuff列表的解决方案
问题背景
用户定义了如下Haskell求和类型:
data CampingStuff = Apple String Int | Banana String Int | Pineapple String Int | Table String Int | Chairs String Int
希望实现一个pickStuff函数,签名为[CampingStuff] -> ??? -> [CampingStuff],要求传入数据构造器(如Apple)后,筛选出列表中所有对应构造器的元素,例如筛选后得到:
Apple "Jane" 3 Apple "Jack" 5 Apple "Lucy" 6
用户尝试的方案
用户最初的实现代码如下:
pickStuffs stuffs dummyStuff = filter (\x -> (x == dummyStuff) ) stuffs -- 调用时需传入无意义占位值 pickStuffs stuffs (Apple "" 0) instance Eq CampingStuff where (Apple name1 number1) == (Apple name2 number2) = True -- 其他构造器的Eq实例未实现
现有方案的缺陷
- 必须传入无意义的占位值(如
""、0),写法冗余且不优雅; - 需要为所有数据构造器手动实现
Eq类型类实例,工作量大且易遗漏; - 扩展性差,无法支持复杂过滤逻辑(例如筛选所有属于Jane的Apple,即匹配
Apple "Jane" _)。
可行解决方案
方案一:手动构造匹配函数(推荐)
为每个数据构造器编写对应的判断函数,再让pickStuff接收该函数作为筛选条件:
-- 定义构造器匹配函数 isApple :: CampingStuff -> Bool isApple (Apple _ _) = True isApple _ = False isBanana :: CampingStuff -> Bool isBanana (Banana _ _) = True isBanana _ = False isPineapple :: CampingStuff -> Bool isPineapple (Pineapple _ _) = True isPineapple _ = False isTable :: CampingStuff -> Bool isTable (Table _ _) = True isTable _ = False isChairs :: CampingStuff -> Bool isChairs (Chairs _ _) = True isChairs _ = False -- 实现pickStuff:本质是对filter的封装 pickStuff :: [CampingStuff] -> (CampingStuff -> Bool) -> [CampingStuff] pickStuff = filter
调用示例
-- 筛选所有Apple元素 pickStuff stuffs isApple -- 扩展:筛选所有属于Jane的Apple pickStuff stuffs (\case Apple "Jane" _ -> True; _ -> False)
这种方案直观易懂,无需依赖任何GHC扩展,且天然支持任意复杂的过滤逻辑。
方案二:借助Data.Data模块自动匹配构造器
如果构造器数量较多,不想手动编写每个匹配函数,可以借助Data.Data模块的功能,通过构造器名称字符串进行筛选:
首先开启GHC扩展并导入模块:
{-# LANGUAGE DeriveDataTypeable #-} import Data.Data -- 修改类型定义,派生Data和Typeable实例 data CampingStuff = Apple String Int | Banana String Int | Pineapple String Int | Table String Int | Chairs String Int deriving (Data, Typeable)
然后实现pickStuff:
pickStuff :: [CampingStuff] -> String -> [CampingStuff] pickStuff stuffs conName = filter (\x -> constrName (toConstr x) == conName) stuffs
调用示例
-- 筛选所有Apple元素 pickStuff stuffs "Apple"
该方案适合构造器数量多的场景,但依赖GHC扩展,且基于字符串匹配,编译期不会检查构造器名称的正确性。
方案三:使用构造器标签类型
定义一个对应构造器的标签类型,通过标签匹配实现筛选:
-- 定义构造器标签类型 data StuffType = AppleType | BananaType | PineappleType | TableType | ChairsType -- 实现标签与构造器的匹配逻辑 matchType :: CampingStuff -> StuffType -> Bool matchType (Apple _ _) AppleType = True matchType (Banana _ _) BananaType = True matchType (Pineapple _ _) PineappleType = True matchType (Table _ _) TableType = True matchType (Chairs _ _) ChairsType = True matchType _ _ = False -- 实现pickStuff pickStuff :: [CampingStuff] -> StuffType -> [CampingStuff] pickStuff stuffs t = filter (`matchType` t) stuffs
调用示例
-- 筛选所有Apple元素 pickStuff stuffs AppleType
该方案具备编译期类型安全性,不会出现拼写错误,同时也支持扩展复杂过滤逻辑。
总结
- 优先选择方案一:简单直观,无额外依赖,灵活性最高;
- 构造器数量多时选方案二:减少手动代码量;
- 追求类型安全性时选方案三:编译期校验避免错误。
内容的提问来源于stack exchange,提问作者Shawn Zhang
相关产品推荐
相关产品推荐

