Let’s play a game. There are two piles we’re going to use, both consisting of black/white sided chips.
data Pile = Pile { _blacks, _whites :: Int }
makeLenses ''Pile
data Game = Game { _pileA, _pileB :: Pile }
makeLenses ''Game
A really clever move would be to turn over a black chip in pile A, and a white chip — in pile B. But how?
cleverMove :: Game -> Game
cleverMove game = game & pileA . blacks -~ 1
& pileA . whites +~ 1
& pileB . blacks +~ 1
& pileB . whites -~ 1
Not very elegant. How can I do it without referencing each pile twice?
The only thing I came up with (and I don’t like it):
cleverMove game = game & pileA %~ (blacks -~ 1)
. (whites +~ 1)
& pileB %~ (blacks +~ 1)
. (whites -~ 1)
(Sorry in advance if it’s obvious — I’m kinda new to lenses and I feel lost in the sea of combinators and operators lens
offers. There’s probably everything for everybody’s needs hiding there. Not that it’s bad, of course! but I wish there was also a complete manual included.)
A Traversal
is a generalization of Lens
which "focuses" on multiple values. Think of it like traverse
which allows you to step through a Traversable t
modifying values in an Applicative
(traverse :: (Applicative f, Traversable t) => (a -> f b) -> t a -> f (t b)
looks pretty much like the type of a Lens
already, you'll notice—just think of t b ~ whole
).
For a Traversal
we can just pick off the values we want to change. For instance, let me generalize your Pile
a bit and built a 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
so as you can see, I visit both the blacks
and the whites
with f
but leave name
pure
. This is almost the same way you write a Traversable
instance except you always visit all of the (homogenous) elements contained in the Traversable
structure.
Main*> Pile 0 0 "test" & counts +~ 1
Pile {_blacks = 1, _whites = 1, _name = "test"}
This isn't enough to do what you want, though, since you need to update your fields in different ways. For that, you need to specify your logic and ensure it upholds an entirely different set of rules.
blackToWhite :: Pile -> Pile
blackToWhite = (blacks -~ 1) . (whites +~ 1)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With