You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 12:55:15