如何在树上使用traversable

时间:2013-10-13 03:15:27

标签: haskell tree-traversal

我试图了解如何使用Traversable迭代我的树数据结构。 我有这棵丑陋的树(实际上是森林),看起来像这样:

data ParseTree a = Leaf a | Node a (ParseTree a) (ParseTree a) | MultiNode a [ParseTree a]
       deriving (Show, Functor, F.Foldable, T.Traversable)

t = Node S 
(Node NP (Leaf DP) (Leaf NP)) 
(MultiNode VP 
[Node VP (Node VP (Leaf VP) (Leaf PP)) (Node NP (Leaf DP) (Leaf NP)),
Node VP (Leaf VP) (Node PP (Leaf PP) (Node NP (Leaf DP) (Leaf NP)))]
)

我想找到多节点,以便我可以替换构建新树,多个节点中的每个项目都有一个。

我很容易编写像这样的函数

findmulti :: ParseTree a -> [ParseTree a]
findmulti (Leaf a) = []
findmulti (Node a left right) = findmulti left ++ findmulti right
findmulti (MultiNode a lst) = [ (MultiNode a lst) ]

但我认为我应该能够使用遍历列表并为我找到项目。 如果我做

traverse print t

我只得到最后的值:

取值 NP DP NP VP VP VP VP PP ...

但我实际上希望遍历停在Multinodes。如何控制遍历的深度?或者这是不可能的,因为Traversable和朋友对容器不可知?

最终我想衍生出可以让我更换多边形的镜头,但是现在我只是想弄清楚可穿越的方式。我甚至使用合适的工具来完成工作吗?

1 个答案:

答案 0 :(得分:4)

构建Traversal'可以轻松访问您喜欢的任何内容,但此功能比Data.Traversable允许的 {/ em> Traversal必须更加通用仅访问并确切地包含所包含的元素。

首先,让我们检查traverse

的签名
traverse :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b)

我喜欢将第一个参数称为“注入”功能。注入Applicativef的任何内容都由Traversal“访问”。在这种情况下,我们想访问MultiNode,以便我们可以专门研究这种类型。

multinodes :: Applicative f => (ParseTree a -> f (ParseTree a)) -> ParseTree a -> (f (ParseTree a))

或者

multinodes :: Traversal' (ParseTree a) (ParseTree a)

现在让我们考虑一下这个定义。同样,目标是注入每个MultiNode

-- No multinodes here, so we use `pure` so as to inject nothing at all
multinodes inj l@Leaf{} = pure l

-- Here's a multinode, so we'll visit it with the injection function
multinodes inj m@Multinode{} = inj m

-- And we need to neither reject (thus stopping the traversal)
-- nor inject a branch. Instead, we continue the traversal deeper
multinodes inj (Node a l r) = 
  (\l' r' -> Node a l' r') <$> multinodes inj l
                             <*> multinodes inj r

这是我们的Traversal'

>>> t ^.. multinodes
[MultiNode VP [Node VP (Node VP (Leaf VP) (Leaf PP)) (Node NP (Leaf DP) (Leaf NP)),Node VP (Leaf VP) (Node PP (Leaf PP) (Node NP (Leaf DP) (Leaf NP)))]]

此代码并不比为findmulti编写的代码短得多 - 实际上,它只不过是展开的findMulti . traverse,但它可以立即与其他lens组合器兼容并演示了通过一种类型喂养Applicative同时瞄准所需内部结构的一般方法。只需编写一次Traversal',就可以成为MultiNode几乎所有访问的通用工具。