使用ekmett的镜头更新字段的多个子字段

Art*_*yom 6 haskell lenses

来玩个游戏.我们将使用两个桩,两个都由黑色/白色侧面芯片组成.

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

data Game = Game { _pileA, _pileB :: Pile }
makeLenses ''Game
Run Code Online (Sandbox Code Playgroud)

一个非常聪明的举动是将A堆中的黑色芯片和白色芯片 - 在B堆中翻转.但是如何?

cleverMove :: Game -> Game
cleverMove game = game & pileA . blacks -~ 1
                       & pileA . whites +~ 1
                       & pileB . blacks +~ 1
                       & pileB . whites -~ 1
Run Code Online (Sandbox Code Playgroud)

不是很优雅.如何在不参考每个桩两次的情况下进行此操作?

我想出的唯一的事情(我不喜欢它):

cleverMove game = game & pileA %~ (blacks -~ 1)
                                . (whites +~ 1)
                       & pileB %~ (blacks +~ 1)
                                . (whites -~ 1)
Run Code Online (Sandbox Code Playgroud)

(如果很明显,请提前抱歉 - 我对镜头不熟悉,我觉得在组合器和操作员lens提供的海洋中迷失了.可能一切都是为了满足每个人的需求.当然不是很糟糕!但我希望有还包括完整的手册.)

J. *_*son 6

A Traversal是对Lens"聚焦"多个值的概括.可以把它想象成traverse允许你逐步Traversable t修改一个修改值Applicative(traverse :: (Applicative f, Traversable t) => (a -> f b) -> t a -> f (t b)看起来非常像Lens已经的类型,你会注意到 - 只是想到t b ~ whole).

对于Traversal我们,我们可以选择我们想要改变的值.例如,让我概括Pile一下并构建一个Traversal.

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
Run Code Online (Sandbox Code Playgroud)

所以你可以看到,我访问了blacks和所有whites,f但离开name pure.这几乎与编写Traversable实例的方式相同,只是您总是访问结构中包含的所有(同质)元素Traversable.

Main*> Pile 0 0 "test" & counts +~ 1
Pile {_blacks = 1, _whites = 1, _name = "test"}
Run Code Online (Sandbox Code Playgroud)

但这并不足以满足您的需求,因为您需要以不同的方式更新字段.为此,您需要指定逻辑并确保它支持完全不同的规则集.

blackToWhite :: Pile -> Pile
blackToWhite = (blacks -~ 1) . (whites +~ 1)
Run Code Online (Sandbox Code Playgroud)