您想遍历整个数据结构并在这里和那里更改一些项目。这通常由一个函数完成,该函数将数据结构作为参数并返回结构的新更改版本。
对于每种输入情况,此函数都定义了返回的新值的外观。
Tree
修改 a (这只是一个值列表)的基本函数DataA
可能应该只返回一个新的修改值列表。如果我们将这些值的修改推迟到一个modifyA
函数中,主要的修改函数如下所示:
-- # 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)
现在mutateA
需要定义该函数以更改所有可能的DataA
值,并且最好附带一个mutateB
处理DataB
值的函数。
这些函数查看值的不同可能情况并返回适当的新值:
-- # 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"
这样对于树中的每个元素都会计算一个新元素,其中DataB2
树中任何位置的所有值都被“foo”替换。
它相对冗长,因为您有五个不同的案例,其中包含需要遍历的值列表,但这并不是 Haskell 特有的。在命令式语言中,您通常有五个 for 循环来代替五个对map
.
也许您可以简化数据结构以减少这种“开销”。这当然取决于您的实际用例,但也许,例如,您不需要这些用例:和Data2
之间有区别吗?DataA2 "abc"
DataA3 "abc" []