正如我们从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>
我的问题:
为什么bar
箭头无法检测到'bar'元素?
如何检测?
答案 0 :(得分:3)
这是类型签名真正有用的案例之一。在类型签名上凝视一秒:
(+=) :: (ArrowXml a) => a b XmlTree -> a b XmlTree -> a b XmlTree
首先,ArrowXml
是Arrow
的子类,它描述了某种输入某些输出的机器。你可以把它想象成一个带有传送带的大工厂,把东西带到不同的机器上,我们正在建造这些工厂机器,因此也就是工厂的功能。例如,三个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)
。