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

如何基于generics-sop实现产品类型访问器的泛型遍历函数?

问题

我已经用generics-sop库实现了如下函数,它接收产品类型的实例值,遍历其所有成员并应用指定函数,最终返回结果列表:

import Generics.SOP qualified as SOP
import Generics.SOP hiding (Generic)

productToList :: forall c a t xs. (
  IsProductType a xs, 
  SOP.Generic a, 
  ProductCode a ~ xs, 
  All c xs
  ) 
  => (forall b. c b => b -> t) -> a -> [t]
productToList f = hcollapse . hcmap (Proxy :: Proxy c) (mapIK f) . productTypeFrom

示例用法如下:

import Generics.SOP qualified as SOP
import Generics.SOP hiding (Generic)

data MyNums = MyNums { anInt :: Int, anInteger :: Integer, aFloat :: Float }
  deriving stock Generic
  deriving anyclass SOP.Generic

class (Eq a, Num a) => EqNum a 
instance (Eq a, Num a) => EqNum a 

c = MyNums { anInt = 0, anInteger = 5, aFloat = 7.2 }

f :: (Eq a, Num a) => a -> Bool
f x = x == 0

y :: [Bool]
y = productToList @EqNum f c

该示例会输出列表[True, False, False]。

现在需要实现一个签名不同的函数:

productAccessorsToList :: ... => (forall b. c b => (a -> b) -> t) -> [t]

该函数无需传入产品类型的实例值,而是遍历目标类型的所有访问器函数,将每个访问器传入指定处理函数后返回结果列表。请问该函数的实现方式是什么?


解答

完整实现

我们可以借助generics-sop的元数据查询和泛型遍历能力来实现这个函数,核心是生成对应类型的所有字段访问器,再传入处理函数转换:

import Generics.SOP qualified as SOP
import Generics.SOP hiding (Generic)
import Generics.SOP.Metadata (constructorInfo, FieldInfo(..))
import Data.Proxy (Proxy(..))

-- 辅助类型:用于标记字段的类型
data FieldProxy a = FieldProxy

productAccessorsToList :: forall c a t xs. (
  SOP.Generic a,
  Code a ~ '[xs],  -- 限制为单构造函数的记录/产品类型
  All c xs,
  SOP.HasDatatypeInfo a
  ) => (forall b. c b => (a -> b) -> t) -> [t]
productAccessorsToList f = hcollapse $ hcmap (Proxy @c) (mapIK f) accessors
  where
    -- 生成所有字段的访问器函数
    accessors :: NP (K (a -> b)) xs
    accessors = hliftA mkAccessor fieldProxies
      where
        mkAccessor :: FieldProxy b -> K (a -> b) xs
        mkAccessor _ = K $ \x -> SOP.hindex (SOP.unSOP $ SOP.from x) (Proxy @b)

    -- 从类型元数据中获取每个字段的类型代理
    fieldProxies :: NP FieldProxy xs
    fieldProxies = case constructorInfo (Proxy @a) of
      SOP.Constructor _ (SOP.Record fields) -> hmap (\_ -> FieldProxy) fields
      _ -> error "productAccessorsToList仅支持记录风格的产品类型"

关键细节说明

  1. 约束解析:

    • SOP.Generic a:让类型a具备泛型表示能力
    • Code a ~ '[xs]:确保目标类型是单构造函数的产品类型(符合记录类型的结构)
    • All c xs:要求所有字段类型都满足约束c
    • SOP.HasDatatypeInfo a:用于读取类型的元数据(比如字段名)
  2. 访问器生成:

    • 通过constructorInfo获取记录的字段元数据,生成对应每个字段的FieldProxy(标记字段类型)
    • 用hliftA将每个FieldProxy转换为访问器函数:\x -> hindex (unSOP (from x)) proxy,这个函数能从实例x中提取对应字段的值
    • 最后通过hcmap将处理函数f应用到每个访问器,再用hcollapse把泛型列表NP转换为普通Haskell列表

示例用法

基于之前的MyNums类型,我们可以这样使用这个函数:

-- 处理函数:接收访问器,返回字段名和访问器的判断结果
processAccessor :: EqNum b => (MyNums -> b) -> (String, Bool)
processAccessor getter = (getFieldName getter, getter c == 0)
  where
    -- 从访问器推导字段名
    getFieldName :: (MyNums -> b) -> String
    getFieldName _ = case constructorInfo (Proxy @MyNums) of
      SOP.Constructor _ (SOP.Record fields) -> case hindex fields (Proxy @b) of
        FieldInfo name -> name
      _ -> ""

-- 获取结果列表
result :: [(String, Bool)]
result = productAccessorsToList @EqNum processAccessor

运行后result的值为[("anInt", True), ("anInteger", False), ("aFloat", False)]。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 06:30:09