【问题标题】:how to use traversable on a tree如何在树上使用可遍历
【发布时间】:2013-10-13 03:15:26
【问题描述】:

我试图了解如何使用 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 向下查找列表并为我找到项目。 如果我这样做了

traverse print t

我只得到最后的值:

S NP DP NP 副总裁 副总裁 副总裁 副总裁 PP...

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

最终我想派生出允许我替换多节点的镜头,但现在我只是想了解可遍历的工作原理。我是否使用了正确的工具来完成这项工作?

【问题讨论】:

    标签: haskell tree-traversal


    【解决方案1】:

    构建一个Traversal' 访问任何你喜欢的东西是相当容易的,尽管这个功能比Data.Traversable 允许的更通用,因为那个 Traversal 必须只访问并且完全包含元素。

    首先,让我们检查traverse的签名

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

    我喜欢将第一个参数称为“注入”函数。任何注入Applicativef 的东西都会被Traversal“访问”。在这种情况下,我们想访问MultiNodes,这样我们就可以专攻这个类型了。

    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' 将成为对MultiNodes 进行几乎任何类型访问的通用工具。

    【讨论】:

    • 非常感谢您为我指明了正确的方向。有几个包括并将“分支”翻译为“节点”,我开始使用镜头运行。我可以说,精彩会随之而来!
    • 啊,哎呀!我重新输入它以检查代码,但显然错过了:) 但是为遍历的力量 +1——祝你好运! (现在为未来的观众修正了错字)
    • 为了让您更进一步,我现在看到您的“多节点”功能几乎就像专门用于我的 ParseTree 类型的 fmap 的应用版本。这对解释它为什么起作用有很长的路要走。更进一步,我如何将其用作二传手?它看起来像“^..”作为吸气剂,而“^.”是二传手吗?我似乎仍然无法完成这项工作。
    • "t ^. multinodes (Leaf VP)" 是我所期望的。做这个把戏
    • 简单地说,(.~) 用于设置(但请注意,它需要 3 个参数,因此它的使用与(^.) 略有不同)。有关更多详细信息,I wrote up a quick primer to Lenses on FP Complete. 这个文档有点绕,但是通过使用透镜、棱镜和遍历((^..) 很有用)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-21
    • 1970-01-01
    • 2018-01-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多