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

如何无需模式匹配所有变体即可访问sum type中的公共字段?

问题

我定义了多个sum type,每个类型的所有变体都包含一个公共字段a,示例代码如下:

data T1 a
  = C1 String a
  | C2 Int a
  | C3 Bool a
data T2 a
  = C4 Int Int a
  | C5 [String] a

请问是否无需对所有类型的所有变体进行模式匹配,就能访问这个a字段?(此问题源于AST定义场景,希望能简洁获取节点特定信息)

解决方案

1. 自定义类型类

可以抽象出一个专门用于提取a字段的类型类,为每个sum type实现该类的实例,之后就能用统一的函数获取字段:

class HasA t where
  getA :: t a -> a

instance HasA T1 where
  getA (C1 _ a) = a
  getA (C2 _ a) = a
  getA (C3 _ a) = a

instance HasA T2 where
  getA (C4 _ _ a) = a
  getA (C5 _ a) = a

使用时直接调用getA即可,无需每次写全模式匹配:

-- 示例:提取T1、T2实例中的a字段
extractT1 :: T1 Int -> Int
extractT1 = getA

extractT2 :: T2 String -> String
extractT2 = getA

2. 重构AST数据结构(推荐)

如果这些sum type是AST节点,更合理的设计是把公共字段a抽出来,用一个统一的包裹类型封装:

-- 定义不同节点的具体内容类型
data ASTContent a
  = NodeStr String
  | NodeInt Int
  | NodeBool Bool
  | NodeDoubleInt Int Int
  | NodeStrList [String]

-- 统一的AST节点类型,包含公共字段a和具体内容
data ASTNode a = ASTNode
  { nodeContent :: ASTContent a
  , nodeA :: a  -- 公共字段直接作为记录字段
  }

这样提取a字段只需直接访问nodeA,完全不需要模式匹配,后续新增AST节点时,只需扩展ASTContent即可,公共逻辑无需修改。

3. 借助泛型自动提取(依赖GHC扩展)

如果不想手动编写类型类实例,可以利用GHC的泛型功能,自动提取每个变体的最后一个字段(要求a始终是变体的最后一个字段)。

首先启用必要的GHC扩展:

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}

然后编写通用的提取函数:

import GHC.Generics
import Generics.SOP

-- 为原有类型派生Generic
data T1 a
  = C1 String a
  | C2 Int a
  | C3 Bool a
  deriving (Generic)

data T2 a
  = C4 Int Int a
  | C5 [String] a
  deriving (Generic)

-- 通用提取最后一个字段的函数
getA :: (Generic (t a), HasDatatypeInfo (t a), All (LastField a) (Code (t a))) => t a -> a
getA = unK . lastField . unSOP . from
  where
    lastField :: All (LastField a) xss => NP I xss -> K a xss
    lastField = hcollapse . hcliftA (Proxy @(LastField a)) (K . getLastField)

-- 定义类型类处理不同长度的变体字段
class LastField a xs where
  getLastField :: I xs -> a

instance LastField a (x : y : z : '[a]) where
  getLastField (I (_, _, _, a)) = a

instance LastField a (x : '[a]) where
  getLastField (I (_, a)) = a

instance LastField a '[a] where
  getLastField (I a) = a

这种方法适合字段位置固定的场景,但需要依赖generics-sop库,且对代码的可读性有一定影响。

内容的提问来源于stack exchange,提问作者string_loginUsername

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 23:15:55