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

Control.Lens进阶:如何将Lens' s a与a→Lens' s b组合为Lens' s b?

实现动态选择的Lens:组合Lens' s a与a -> Lens' s b的优雅方式

在回合制双人游戏的模拟场景中,我们定义了如下数据结构及自动生成的Lens:

data PlayerState = PlayerState { _score :: Int, ... }
$(makeLenses ''PlayerState)

data Player = PlayerA | PlayerB

data GameState = GameState { _playerA, _playerB :: PlayerState,
                             _curPlayer :: Player }
$(makeLenses ''GameState)

通过指定玩家获取对应状态的Lens很容易实现:

playerState :: Player -> Lens' GameState PlayerState
playerState PlayerA = playerA
playerState PlayerB = playerB

现在需要实现一个聚焦当前玩家状态的Lens curPlayerState,支持someGame & curPlayerState.score += 42这类便捷操作。当前手动拆分getter和setter的实现方式可行,但希望找到更通用的组合方式——将Lens' s a(如curPlayer)与a -> Lens' s b(如playerState)组合为Lens' s b,逻辑上类似Monad的>>=绑定操作。


通用组合子实现

可以封装一个通用组合子来复用这种动态Lens的逻辑:

import Control.Lens

-- 组合Lens' s a与a->Lens' s b,得到聚焦b的Lens' s b
lensBind :: Lens' s a -> (a -> Lens' s b) -> Lens' s b
lensBind l f = lens getter setter
  where
    getter s = s ^. f (s ^. l)  -- 先通过l提取a,再用f获取对应Lens并取值
    setter s b = s & f (s ^. l) .~ b  -- 先通过l提取a,再用f获取对应Lens并设置值

借助这个组合子,curPlayerState可以简洁实现:

curPlayerState :: Lens' GameState PlayerState
curPlayerState = lensBind curPlayer playerState

合法性说明

这个组合后的curPlayerState完全符合Lens的三大定律:

  1. 恒等性:view curPlayerState s & curPlayerState .~ view curPlayerState s 等价于原s
  2. 组合性:设置值后再取值,结果等于设置的目标值
  3. 一致性:连续两次设置的结果,与直接设置第二次的值完全相同

原因在于,每个GameState实例的curPlayer只会返回PlayerA或PlayerB,对应的playerState是合法的Lens,因此整个组合逻辑是可靠的。


紧凑内联写法

如果不想单独定义组合子,也可以用更紧凑的内联方式实现,本质与原始实现一致,但代码更简洁:

curPlayerState :: Lens' GameState PlayerState
curPlayerState = lens (\g -> g ^. playerState (g ^. curPlayer))
                      (\g s -> g & playerState (g ^. curPlayer) .~ s)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 18:35:55