+ =运算符的固执

时间:2015-05-08 18:29:04

标签: xml haskell hxt

正如我们从my previous question那里学到的, += 是允许一次添加一个元素的运算符。是否有可能 «检测»以前添加的元素并控制未来的添加方式 做什么?

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

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. 如何检测?

3 个答案:

答案 0 :(得分:3)

这是类型签名真正有用的案例之一。在类型签名上凝视一秒:

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

首先,ArrowXmlArrow的子类,它描述了某种输入某些输出的机器。你可以把它想象成一个带有传送带的大工厂,把东西带到不同的机器上,我们正在建造这些工厂机器,因此也就是工厂的功能。例如,三个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 

显然,我们正在使用两台机器将b转换为XmlTree并将它们“合并”到一台机器中,该机器需要b并驱逐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 ((.))

答案 1 :(得分:1)

这是可行的解决方案,感谢@ChrisDrost:

infixl 7 ++=

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

根据我的理解,+=只是传递其输入,因为它的类型为b, 无论如何它无论如何也无济于事。所以,它只是通过链 运营商不变。在这里,我们对该输入加强了约束 ++=可以是:{必须是XmlArrow。我们把这个箭头喂给第二个 ++=的参数,因此它知道XML文档的“更新”状态。

答案 2 :(得分:0)

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

另一方面,arrows包似乎提供了可能有用的stateful arrow transformer。大概你可以写一个instance ArrowXml a => ArrowXml (StateArrow s a)