2

这个问题是关于 HXT 的,但我想它通常适用于概念 ArrowPlus。考虑以下程序:

module Main (main) where

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

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

foo :: ArrowXml a => a XmlTree XmlTree
foo = selem "foo" [bar >>> bar >>> bar]

bar :: ArrowXml a => a XmlTree XmlTree
bar = this <+> eelem "bar"

你能告诉我会保存test.xml什么吗?我的期望:

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

我的逻辑:箭头bar复制其所有输入并添加一个“条”元素(this是标识箭头的别名):

 |     |
this  eelem "bar"
 |     |
 \     /
  \   /
   <+>
    |

因此,结果bar >>> bar >>> bar应该是三个 'bar' 元素(请注意,这eelem "bar" >>> eelem "bar"只会产生一个 'bar' 元素,因为箭头mkelem家族忽略了它们的输入(尽管它仍然可以用来生成它们的内容)并且只输出新创建的元素) .

说了这么多,我介绍test.xml一下程序执行后的内容:

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

问题:

  1. 是什么<//>

  2. 为什么有 7 个“条形”元素而不是 3 个?这种重复的原因是什么?

  3. 为什么当我替换bar >>> bar >>> barnone >>> bar >>> bar >>> bar我得到:

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

none零箭头在哪里。我们在这里处理箭头上的幺半群,对吧? none(≡ zeroArrow) 应该是它的标识,所以它应该是这样的: none <+> eelem "bar"它产生一个 'bar' 元素,随后的调用应该添加另外两个元素。但是我们什么都没有!

  1. 如何编写bar一次添加一个“条形”元素的正确版本的箭头?

很抱歉问了 4 个问题,但我想它们是密切相关的,所以应该不是问题。

4

1 回答 1

2

您似乎对>>><+>运算符的工作方式有些困惑。为了建立直觉,我们首先定义两个不同bar的 s:

bar1 :: ArrowXml a => a XmlTree XmlTree
bar1 = this <+> eelem "bar"

bar2 :: ArrowXml a => a n XmlTree
bar2 = eelem "bar"

我们首先注意到的是类型签名。bar1输入类型为XmlTree,这意味着它以某种方式修改现有树,而bar2丢弃其参数。这是由于使用thisinbar1复制其元素。现在,让我们加载这些内容ghci以了解如何协同工作>>><+>

Prelude Text.XML.HXT.Core> runX $ xshow $ bar2
["<bar/>"]
Prelude Text.XML.HXT.Core> runX $ xshow $ bar2 >>> bar2 >>> bar2
["<bar/>"]

嗯,这很奇怪,无论我们用多少次组合它,它都会不断创建相同的结构>>>。发生这种情况是因为对于bar2,我们每次转换它时都会丢弃树:记住它的类型签名是a n XmlTree而不是a XmlTree XmlTree. 让我们将其与以下内容进行比较bar1

Prelude Text.XML.HXT.Core> runX $ xshow $ bar1
["<//><bar/>"]
Prelude Text.XML.HXT.Core> runX $ xshow $ bar1 >>> bar1
["<//><bar/><bar/><bar/>"]
Prelude Text.XML.HXT.Core> runX $ xshow $ bar1 >>> bar1 >>> bar1
["<//><bar/><bar/><bar/><bar/><bar/><bar/><bar/>"]

哇,它正在成倍增长!为什么?好吧,每次你用 编写时>>>,你都在使用前一个树,并为每个元素应用函数 application this <+> eelem "bar"。第一次调用bar1没有先前的树,因此this成为根节点,您只需<bar/>向其附加一个元素。但是,对于bar1 >>> bar1,第一个bar1将创建<//><bar/>,第二个将再次组成<//><bar/>with的每个节点bar1,从而导致:

bar1 === <//><bar/>
bar1 >>> bar1 === <//><bar/><bar/><bar/>
                  |--------||----------|
                    First      Second

现在你继续这样做,你可以看到bar1 >>> bar1 >>> bar1将如何产生 7 <bar/>s 前面有 a <//>

好的,现在我们有了直觉>>>ArrowXml我们可以看到<+>行为方式:

Prelude Text.XML.HXT.Core> runX $ xshow $ bar2 <+> bar2 <+> bar2
["<bar/><bar/><bar/>"]
Prelude Text.XML.HXT.Core> runX $ xshow $ bar1 <+> bar1 <+> bar1
["<//><bar/><//><bar/><//><bar/>"]

哦,这很简单......他们只是一个接一个地附加。我们可以看到 of 的类型bar1 <+> bar1仍然是转换类型值的类型a XmlTree XmlTree,所以如果将它与>>>. 看看你是否可以围绕这个输出思考:

Prelude Text.XML.HXT.Core> runX $ xshow $ (bar1 <+> bar1) >>> bar1
["<//><bar/><bar/><bar/><//><bar/><bar/><bar/>"]
Prelude Text.XML.HXT.Core> runX $ xshow $ bar1 >>> (bar1 <+> bar1)
["<//><bar/><//><bar/><bar/><bar/><bar/><bar/>"]

要回答关于 的问题none,请检查它的类型签名:

Prelude Text.XML.HXT.Core> :t none
none :: ArrowList a => a b c

对我来说,它需要一个 type 的值b并返回一个 type 的值c。由于我们不知道是什么c(我们没有将它作为参数提供给none),我们可以假设它是空集。>>>这对于我们之前对and的定义是有意义的<+>

Prelude Text.XML.HXT.Core> runX $ xshow $ none <+> bar1
["<//><bar/>"]
Prelude Text.XML.HXT.Core> runX $ xshow $ none >>> bar1
[""]

在第一种情况下,空文档附加另一个文档基本上是身份操作。在第二个中,none不产生任何元素,所以当你用组合它时bar1,没有要操作的元素,因此结果是空文档。事实上,由于 Haskell 是懒惰的,我们可以更加随意地处理我们的组合,none因为我们知道它永远不会被评估:

Prelude Text.XML.HXT.Core> runX $ xshow $ none >>> undefined
[""]

鉴于这些知识,您可能尝试做的是这样的事情:

Prelude Text.XML.HXT.Core> let bar = eelem "bar"
Prelude Text.XML.HXT.Core> runX $ xshow $ selem "foo" [bar <+> bar <+> bar]
["<foo><bar/><bar/><bar/></foo>"]

编辑

类似的解决方案是使用+=运算符:

Prelude Text.XML.HXT.Core> let bars = replicate 3 (eelem "bar")
Prelude Text.XML.HXT.Core> runX $ xshow $ foldl (+=) (eelem "foo") bars
["<foo><bar/><bar/><bar/></foo>"]
于 2015-05-06T21:43:28.067 回答