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的三大定律:
- 恒等性:
view curPlayerState s & curPlayerState .~ view curPlayerState s等价于原s - 组合性:设置值后再取值,结果等于设置的目标值
- 一致性:连续两次设置的结果,与直接设置第二次的值完全相同
原因在于,每个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
相关产品推荐
相关产品推荐

