1

正如我们从我之前的问题中了解到的, += 是允许一次添加一个元素的运算符。是否可以“检测”先前添加的元素并控制未来添加的方式?

这是一个开始我们调查的简单程序:

module Main (main) where

import Control.Monad (void)
import Text.XML.HXT.Core

main :: IO ()
main = void $ runX $ root [] [foo]
       >>> writeDocument [withIndent yes] "test.xml"

foo :: ArrowXml a => a XmlTree XmlTree
foo = eelem "foo" += bar += bar += bar -- += is left associative

bar :: ArrowXml a => a XmlTree XmlTree
bar = ifA (deep (hasName "bar")) (eelem "baz") (eelem "bar")

在这里,foo创建 'foo' 节点及其内容。内容是用 bar箭头生成的,它应该足够聪明,可以检测到之前添加的“bar”元素,并改变它的行为。在这里我们deep为了简单起见:如果 'bar' 元素是 'foo' 元素的子元素,无论它有多深,都应该检测到它(getChildren >>> hasName "bar"也应该这样做)。

因此,文件的预期内容test.xml是:

<?xml version="1.0" encoding="UTF-8"?>
<foo>
  <bar/>
  <baz/>
  <baz/>
</foo>

当然,它不起作用。这是我得到的:

<?xml version="1.0" encoding="UTF-8"?>
<foo>
  <bar/>
  <bar/>
  <bar/>
</foo>

我的问题:

  1. bar为什么箭头无法检测到“bar”元素?

  2. 如何检测它?

4

3 回答 3

3

这是类型签名真正有用的情况之一。盯着类型签名一秒钟:

(+=) :: (ArrowXml a) => a b XmlTree -> a b XmlTree -> a b XmlTree 

首先,ArrowXml是 的一个子类Arrow,它描述了某种将某些输入输出到某些输出的机器。你可以把它想象成一个大工厂,用传送带把东西送到不同的机器上,我们正在建造这些工厂机器,因此工厂也有功能。例如,三个箭头组合器是:

(&&&) :: (Arrow a) => a b c -> a b c' -> a b (c, c') |infixr 3|
  Fanout: send the input to both argument arrows and combine their output.

arr :: (Arrow a) => (b -> c) -> a b c
  Lift a function to an arrow.

(.) :: (Category cat) => cat b c -> cat a b -> cat a c
  morphism composition.

现在非常仔细地查看小写字母(类型变量):

(+=) :: (ArrowXml a) => a b XmlTree -> a b XmlTree -> a b XmlTree 

显然,我们将两台机器将bs 转换为XmlTrees 并将它们“合并在一起”成为一台机器,该机器接收 ab并驱逐 a XmlTree。但重要的是,这种类型签名告诉我们,或多或少唯一可以实现的方法是:

arr1 += arr2 = arr f . (arr1 &&& arr2) where
    f :: (XmlTree, XmlTree) -> XmlTree
    f = _

这是因为“自由定理”;如果你不知道参数的类型,那么我们可以证明你真的不能用它很多事情。(它可能有点复杂,因为箭头可能具有不完全封装的结构arr,例如内部计数器,它们与它们相加,.然后0在使用时设置为arr。所以实际上arr f用泛型替换,a (XmlTree, XmlTree) XmlTree你很好去。)

所以我们必须在这里并行执行两个箭头。这就是我想说的。因为组合(+=)器不知道是什么b,所以它别无选择,只能愉快地将b并行馈送到箭头,然后尝试将它们的输出组合在一起。所以,deep (hasName "bar")不看foo

如果你真的想要,你可以用一个相互递归的解决方案deep (hasName "bar") . foo,但它似乎有潜在的危险(即无限循环),所以简单地定义类似的东西可能更安全:

a ++= b = a += (b . a)

其中“当前” a 被“馈送”到 b 以产生更新。为此,您必须从导入.Control.Category因为它与Prelude..(仅执行功能组合)不同。这看起来像:

import Prelude hiding ((.))
import Control.Category ((.))
于 2015-05-09T00:04:54.927 回答
1

感谢@ChrisDrost,这是有效的解决方案:

infixl 7 ++=

(++=) :: ArrowXml a => a XmlTree XmlTree -> a XmlTree XmlTree ->
         a XmlTree XmlTree
a ++= b = a += (a >>> b)

在我的理解中,+=只需传递它的输入,因为它有 type b,所以无论如何它不能做太多事情。因此,它只是通过不变的运营商链。在这里,我们对 can be 的输入施加了更强的约束 ++=:它必须是一个XmlArrow. 我们将此箭头提供给 的第二个参数++=,因此它知道 XML 文档的“更新”状态。

于 2015-05-09T06:00:15.413 回答
0

猜测一下:anArrowXml旨在将 XML 树作为输入并生成 XML 树作为输出。hasName查询输入;eelem修改输出。我会很惊讶地发现有一种方法可以查询输出或修改输入。

另一方面,该arrows软件包似乎提供了一个可能有用的状态箭头转换器。大概你可以写一个instance ArrowXml a => ArrowXml (StateArrow s a).

于 2015-05-08T19:13:43.810 回答