You want to traverse the whole data structure and change some items here and there.
This is usually done by a function that takes the data structure as a parameter and
returns the new, changed version of the structure.
For every case of input this function defines how the new value that is returned should
look like.
The basic function that modifies a Tree
(which is just a list of DataA
values)
probably should just return a new list of modified values. If we defer those
modifications of the values to a modifyA
function, the main modification
function looks like this:
-- # function to change a |Tree|
mutate :: Tree -> Tree
mutate as = map mutateA as
-- # (The |map| function applies the |mutateA| function to every
-- # element of |as|, creating a list of all the return values)
Now the mutateA
function needs to be defined to change all possible DataA
values, and
best it is accompanied by a mutateB
function that handles DataB
values.
These functions look at the different possible cases of values and return the
appropriate new values:
-- # function to change |DataA| items
mutateA :: DataA -> DataA
-- # A |DataA1| is a |DataA1| with modified values
mutateA (DataA1 bs) = DataA1 (map mutateB bs)
-- # A |DataA3| is a |DataA3| with modified values
mutateA (DataA3 s as) = DataA3 s (map mutateA as)
-- # In the remaining case(s) the value stays the same
mutateA d = d
-- # function to change |DataB| items
mutateB :: DataB -> DataB
mutateB (DataB1 as) = DataB1 (map mutateA as)
mutateB (DataB3 s bs) = DataB3 s (map mutateB bs)
-- # Here comes a real change
mutateB (DataB2 _) = DataB2 "foo"
This way for every element in the tree a new element is computed, where all the DataB2
values anywhere in the tree are replaced by "foo".
It's relatively verbose because you have five different cases that contain a list
of values that needs to be walked through, but that is not specific to Haskell. In
an imperative language you would usually have five for loops in place of the five
calls to map
.
Maybe you could simplify your data structure to reduce this "overhead". This
of course depends on your actual use case, but maybe, for example, you don't need
the Data2
cases:
Is there a difference between DataA2 "abc"
and DataA3 "abc" []
?