Say I have a nested structure as follows:
data Bar = Bar { _id :: Integer, _bars :: [Bar] }
data Foo = Foo { _bars :: [Bar] }
And I have a Foo
with a bunch of Bars
with various id
s:
foo = Foo [Bar 1 [Bar 2], Bar 3 [Bar 4, Bar 5]]
How do, perhaps using lenses, I modify foo
such that Bar 5
becomes Bar 6
?
I know I use fclabels
to do something like this:
mkLabel ''Foo
mkLabel ''Bar
modify bars (\bars -> ...) foo
But bars can be nested infinitely. How do I locate and modify the Bar
with a specified ID?
Yep, lens
can do that. The Control.Lens.Plated
module contains tools for "Scrap Your Boilerplate"-style programming with self-similar structures like your Bar
. The idea is seductively simple: you explain how to find the immediate children of a node (by writing a Traversal' a a
) and the library recursively applies that traversal to the whole structure.
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens
data Bar = Bar { _lbl :: Int, _bars :: [Bar] } deriving (Show)
makeLenses ''Bar
instance Plated Bar where
plate = bars.traverse
(If you don't want to implement plate
yourself, you can derive Data
and leave the instance
empty.)
transform :: Plated a => (a -> a) -> a -> a
takes a function which modifies a single node and applies it to the whole structure.
fiveToSix :: Bar -> Bar
fiveToSix = transform go
where go bar
| bar^.lbl == 5 = bar & lbl .~ 6
| otherwise = bar
Using the example from your question:
ghci> let bars = [Bar 1 [Bar 2 []], Bar 3 [Bar 4 [], Bar 5 []]]
ghci> map fiveToSix bars
[Bar 1 [Bar 2 []], Bar 3 [Bar 4 [], Bar 6 []]]
As another example, for funzies, let's use cosmos
to pull all of the Bar 5
s out of a Bar
.
fives :: Bar -> [Bar]
fives = toListOf $ cosmos.filtered (views lbl (== 5))