Haskell 使用ekmett’更新字段的多个子字段;s透镜

Haskell 使用ekmett’更新字段的多个子字段;s透镜,haskell,lenses,Haskell,Lenses,让我们玩一个游戏。我们将要使用两个堆,都由黑/白边芯片组成 data Pile = Pile { _blacks, _whites :: Int } makeLenses ''Pile data Game = Game { _pileA, _pileB :: Pile } makeLenses ''Game 一个非常聪明的做法是在A堆中翻转一个黑色的芯片,在B堆中翻转一个白色的芯片。但是如何翻转呢 cleverMove :: Game -> Game cleverMove game =

让我们玩一个游戏。我们将要使用两个堆,都由黑/白边芯片组成

data Pile = Pile { _blacks, _whites :: Int }
makeLenses ''Pile

data Game = Game { _pileA, _pileB :: Pile }
makeLenses ''Game
一个非常聪明的做法是在A堆中翻转一个黑色的芯片,在B堆中翻转一个白色的芯片。但是如何翻转呢

cleverMove :: Game -> Game
cleverMove game = game & pileA . blacks -~ 1
                       & pileA . whites +~ 1
                       & pileB . blacks +~ 1
                       & pileB . whites -~ 1
不太优雅。我如何在不引用每一堆两次的情况下完成它

我想到的唯一一件事(我不喜欢):


(如果这是显而易见的,请提前道歉-我对镜头有点陌生,我感觉迷失在组合器和操作符的海洋中
lens
provides。那里可能藏着满足每个人需求的所有东西。当然,这并不坏!但我希望还包括一本完整的手册。)A
Traversal
Lens
的泛化,它“关注”多个值。可以将其想象为
遍历
,它允许您单步通过
可遍历的t
修改
应用程序
中的值(
遍历::(应用程序f,可遍历的t)=>(a->f b)->t a->f(t b)
看起来已经很像
镜头的类型了,你会注意到只要想想
tb~整个

对于
遍历
,我们只需选择要更改的值即可。例如,让我概括一下您的
,并构建一个
遍历

data Pile = Pile { _blacks :: Int, _whites :: Int, _name :: String } deriving (Show)
$(makeLenses ''Pile)

counts :: Traversal' Pile Int
counts f (Pile blacks whites name) = 
  Pile <$> f blacks <*> f whites <*> pure name
但是,这还不足以满足您的需要,因为您需要以不同的方式更新字段。为此,您需要指定您的逻辑,并确保它支持一套完全不同的规则

blackToWhite :: Pile -> Pile
blackToWhite = (blacks -~ 1) . (whites +~ 1)

您不喜欢选项2的哪些方面?没有比这更简洁的了,是吗?@leftaroundabout我不喜欢这样,我不得不用括号,当涉及到多行表达式时,这会变得很笨拙-例如
do
块和更高级别的嵌套。我认为如果您显示一些与理想语法对应的粗略伪代码会有所帮助。@GabrielGonzalez我的理想语法与我的“非理想”语法相同,只是没有括号。问题是,我想知道惯用的解决方案,如果有(因为更新多个子字段应该是一项相当常见的任务,我会惊讶地知道没有)。如果单个更新开始占用多行,可能是时候退一步,启动
let
where
块,然后开始命名。
Main*> Pile 0 0 "test" & counts +~ 1
Pile {_blacks = 1, _whites = 1, _name = "test"}
blackToWhite :: Pile -> Pile
blackToWhite = (blacks -~ 1) . (whites +~ 1)