一次添加一个元素的箭头

Arrow to add one element at a time

这个问题是关于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 复制所有输入并添加一个“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 个“bar”元素而不是 3 个?这是什么原因 重复?

  3. 为什么当我用 none >>> bar >>> bar >>> bar 替换 bar >>> bar >>> bar 时,我得到:

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

其中 none 是零箭头。我们在这里处理箭头上的幺半群,对吗? none (≡ zeroArrow) 应该是它的身份,所以它应该是这样的: none <+> eelem "bar" 产生一个‘bar’元素和后续 调用应该添加另外两个元素。但是我们什么也没有!

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

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

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

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

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

我们首先注意到的是类型签名。 bar1 的输入类型为 XmlTree,这意味着它以某种方式修改了现有的树,而 bar2 则丢弃了它的参数。这是由于在 bar1 中使用 this 复制了它的元素。现在,让我们将它们加载到 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/>"]

哇,它呈指数级增长!为什么?好吧,每次使用 >>> 进行组合时,您都会采用前一棵树,并且对于应用函数应用程序 this <+> eelem "bar" 的每个元素。 bar1 的第一次调用没有前一棵树,因此 this 成为根节点,您只需将 <bar/> 的元素附加到它即可。然而,对于 bar1 >>> bar1,第一个 bar1 将创建 <//><bar/>,第二个将再次将 <//><bar/> 的每个节点与 bar1 组合,导致:

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

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

好的,现在我们已经根据 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/>"]

哦,这很简单...他们只是一个接一个地附加。我们可以看到 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

对我来说,它需要一个 b 类型的值,returns 需要一个 c 类型的值。由于我们不知道 c 是什么(我们没有将它作为 none 的参数提供),我们可以假设它将是空集。这与我们之前对 >>><+>:

的定义是有道理的
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>"]