让我们玩一个游戏。我们将要使用两个堆,两个堆都包含黑/白边盘。
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
提供的海洋中迷失了方向。可能一切都是为了满足每个人的需求。不是很糟糕,当然!但我希望还有一本完整的手册。)
答案 0 :(得分:6)
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
如您所见,我访问了blacks
和whites
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)