我对Haskell很新(仍在努力完全理解monads)。我有一个问题,我有一个类似树的结构
type Tree = [DataA]
data DataA = DataA1 [DataB]
| DataA2 String
| DataA3 String [DataA]
deriving Show
data DataB = DataB1 [DataA]
| DataB2 String
| DataB3 String [DataB]
deriving Show
我想要做的是能够遍历这个并生成一个带过滤器的新树。例如,我可能想要将树中的所有DataB2更改为“foo”。
我看过树的例子,当它们在同一个数据部分时,构造函数是相似的。
在python世界中,我只是遍历列表,匹配我需要的任何内容,并替换值。
在Haskell中我猜我需要能够复制我的树,但是你如何处理隐藏在构造函数和不同数据类型中的列表呢?
答案 0 :(得分:10)
您可以使用泛型编程。
一个这样的通用编程库叫做Scrap Your Boilerplate。在模块的最顶层,通过编写:
启用Scrap Your Boilerplate{-# LANGUAGE DeriveDataTypeable #-}
导入模块Data.Generics
。除Show
之外,还为您的数据类型派生Typeable
和Data
个实例。
现在您可以像这样编写您要求的功能:
toFoo :: Data a => a -> a
toFoo = everywhere (mkT step)
where
step (DataA2 _) = DataA2 "foo"
step x = x
这就是你需要做的就是让它发挥作用。例如,当您致电toFoo [DataA1 [], DataA2 "hi", DataA3 "yo" []]
时,答案为[DataA1 [],DataA2 "foo",DataA3 "yo" []]
。
希望这有帮助!
答案 1 :(得分:2)
我不知道你问题的一般答案。数据类型非常人为,我可能会选择实现折叠而不是过滤器。但是,这里有一些过滤函数可以更新所有四个位置的字符串。我已经把代码放在了编译器中,所以它有了类型,但我还没有运行它。
type SFilter = String -> String
-- to filter a tree, say how A2, A3, B2, and B3 should be changed
type Filter tree = SFilter -> SFilter -> SFilter -> SFilter -> (tree -> tree)
afilter :: Filter DataA
bfilter :: Filter DataB
tfilter :: Filter Tree
tfilter a2 a3 b2 b3 = map (afilter a2 a3 b2 b3)
afilter a2 a3 b2 b3 = fil
where fil (DataA1 bs) = DataA1 $ map (bfilter a2 a3 b2 b3) bs
fil (DataA2 s) = DataA2 (a2 s)
fil (DataA3 s as) = DataA3 (a3 s) (map fil as)
bfilter a2 a3 b2 b3 = fil
where fil (DataB1 as) = DataB1 $ map (afilter a2 a3 b2 b3) as
fil (DataB2 s) = DataB2 (b2 s)
fil (DataB3 s bs) = DataB3 (b3 s) (map fil bs)
答案 2 :(得分:2)
您希望遍历整个数据结构并在此处更改某些项目。 这通常由将数据结构作为参数的函数来完成 返回结构的新的更改版本。
对于输入的每个情况,此函数定义返回的新值的方式 看起来像。
修改Tree
的基本功能(只是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" []
之间有区别吗?
答案 3 :(得分:0)
您可能需要查看multirec库以处理相互递归的数据类型。我没有使用它,但从你所描述的内容来看,它听起来就像是你正在使用的那种问题。它使用泛型编程,就像这里提出的其他答案一样,但可能会节省你自己实现所有内容的时间。