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

如何使用Lens更新包含Maybe的Haskell嵌套记录

解决方案

1. 准备工作:启用扩展与导入模块

首先需要启用必要的Haskell扩展,并导入依赖的库模块:

{-# LANGUAGE TemplateHaskell #-}

import Data.Text (Text)
import Control.Lens (set, makeLenses, non)
import Data.Monoid (Monoid(..))

2. 定义数据类型与自动生成Lens

使用TemplateHaskell自动生成数据类型的Lens(手动编写Lens也可,自动生成更高效):

data RecA = RecA 
  { _aField1 :: Maybe Text
  , _aField2 :: Maybe RecB
  } deriving (Show)

data RecB = RecB
  { _bField1 :: Maybe Text
  , _bField2 :: Maybe Text
  } deriving (Show)

makeLenses ''RecA
makeLenses ''RecB

3. 定义Monoid实例

为嵌套类型定义Monoid实例,确保遇到Nothing时能自动生成空的默认结构:

instance Monoid RecB where
  mempty = RecB Nothing Nothing
  -- 合并逻辑:保留已有非Nothing的值,避免被空值覆盖
  mappend (RecB b1a b2a) (RecB b1b b2b) = RecB (b1a <|> b1b) (b2a <|> b2b)

instance Monoid RecA where
  mempty = RecA Nothing Nothing
  mappend (RecA a1a a2a) (RecA a1b a2b) = RecA (a1a <|> a1b) (a2a <> a2b)

4. 实现深度嵌套更新

利用lens库的non组合子,穿透所有Maybe层完成更新,无论父级是Nothing还是Just:

let myVal = Nothing :: Maybe RecA
    updatedVal = set (non mempty . aField2 . non mempty . bField1) (Just "ValueFor_bField1") myVal

关键说明

  • non mempty:lens提供的棱镜工具,能将Maybe a类型转换为a处理——原类型是Nothing时用mempty(我们定义的空结构)替代,是Just x时直接使用x。
  • 组合路径non mempty . aField2 . non mempty . bField1:依次穿透Maybe RecA→RecA的aField2(Maybe RecB)→RecB的bField1(Maybe Text),自动处理所有Nothing层,创建必要的默认结构后设置目标值。
  • 最终updatedVal会生成Just (RecA Nothing (Just (RecB (Just "ValueFor_bField1") Nothing))),完全满足需求。

手动定义Lens的替代方案

如果不想使用TemplateHaskell,可以手动编写Lens:

aField2 :: Lens' RecA (Maybe RecB)
aField2 = lens _aField2 (\rec newVal -> rec { _aField2 = newVal })

bField1 :: Lens' RecB (Maybe Text)
bField1 = lens _bField1 (\rec newVal -> rec { _bField1 = newVal })

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 17:02:23