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

通用结合Profunctor:基于generics-sop实现recordSelectorToData函数

用generics-sop自动生成autodocodec的记录Codec

目标函数定义

我想要实现的核心函数如下:

recordSelectorToData :: forall c a f xs. 
  (All c xs, IsProductType a xs, Applicative f) => 
  (forall b. c b => (a -> b) -> f b) -> f a

其中All和IsProductType来自generics-sop库,参数里的(a -> b)是当前处理字段的记录选择器。它的特化函数profunctorToData能更清晰体现用途:

profunctorToData :: forall c a p xs. 
  (All c xs, IsProductType a xs, Profunctor p, Applicative (p a)) =>
  (forall b. c b => p b b) -> p a a
profunctorToData x = recordSelectorToData @c (`lmap` x)

核心逻辑:如果类型是积类型、所有字段满足约束c,且能为每个字段构造Profunctor(其末值是Applicative),就能组合所有字段的Profunctor得到对应积类型的Profunctor。

autodocodec场景需求

我基于autodocodec库开发,它能通过一次定义生成ToJSON、FromJSON和ToSchema实例,核心类型Codec具备Functor、Applicative特性和lmapCodec函数,可以定义为Profunctor。

已手动为部分类型定义了Codec,但对于像MyRecord1这种派生Generic的记录类型,希望借助FieldName类自动生成Codec:

data MyRecord1 = MyRecord1 
{ firstName :: FirstName
, lastName :: LastName
} deriving stock (Generic)

class HasCodec a => FieldName a where
  fieldName :: Text

instance FieldName FirstName where
  fieldName = "firstname"

instance FieldName LastName where
  fieldName = "sirname" 

我还写了makeObjectCodec函数:

makeObjectCodec :: (HasCodec b, FieldName b) => (a -> b) -> Object a b
makeObjectCodec f = requiredField (fieldName @b) .= f

期望通过recordSelectorToData @FieldName makeObjectCodec生成MyRecord1的Codec,但不清楚如何从generics-sop中提取可用的记录选择器函数,只知道需要用projections,求具体实现方案。

实现方案

要实现recordSelectorToData,关键是利用generics-sop的projections函数获取记录的所有字段选择器,再结合Applicative的组合能力构造最终的f a。

完整实现代码

首先导入必要的模块:

import Generics.SOP
import Generics.SOP.NP
import Control.Applicative

然后实现recordSelectorToData:

recordSelectorToData :: forall c a f xs. 
  (All c xs, IsProductType a xs, Applicative f) => 
  (forall b. c b => (a -> b) -> f b) -> f a
recordSelectorToData f = to . hsequence $ hcpure (Proxy @c) (\proj -> f (unK proj))
  where
    -- 获取当前积类型的所有字段选择器,包装成NP (K (a -> b)) xs
    projections :: NP (K (a -> b)) xs
    projections = projections' (Proxy @xs) (from (error "unused" :: a))

这里的projections'是generics-sop中projections的类型特化版本,它能从a的泛型表示中提取出所有字段的选择器函数,每个选择器被包装在K新类型里。

通过hcpure为每个选择器应用f,得到一个NP f xs,再用hsequence把这个积类型的Applicative值转换成f (NP I xs),最后用to把泛型表示转换回原始类型a。

测试生成Codec

现在可以用这个函数为MyRecord1生成Codec:

instance HasCodec MyRecord1 where
  codec = objectCodec $ recordSelectorToData @FieldName makeObjectCodec

这样就能自动为MyRecord1生成包含firstname和sirname字段的Codec,无需手动编写每个字段的定义。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 12:53:13