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

时间:2013-06-03 00:04:31

标签: 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 = game & pileA . blacks -~ 1
                       & pileA . whites +~ 1
                       & pileB . blacks +~ 1
                       & pileB . whites -~ 1

不是很优雅。如果不两次引用每一堆,我该怎么办呢?

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

cleverMove game = game & pileA %~ (blacks -~ 1)
                                . (whites +~ 1)
                       & pileB %~ (blacks +~ 1)
                                . (whites -~ 1)

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

1 个答案:

答案 0 :(得分:6)

TraversalLens的概括,它“关注”多个值。您可以将其视为traverse,它允许您逐步执行Traversable tApplicative修改值(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

如您所见,我访问了blackswhites f,但请name pure。这几乎与编写Traversable实例的方式相同,只是您始终访问Traversable结构中包含的(同质)元素的所有

Main*> Pile 0 0 "test" & counts +~ 1
Pile {_blacks = 1, _whites = 1, _name = "test"}

这还不足以做你想做的事情,因为你需要以不同的方式更新字段。为此,您需要指定逻辑并确保它支持完全不同的规则集。

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