+= 运算符的顽固性
Stubbornness of += operator
正如我们从 我之前学到的
问题,
+=
是允许一次添加一个元素的运算符。是否有可能
«检测»先前添加的元素并控制未来添加的方式
完成了吗?
这里有一个简单的程序来开始我们的调查:
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'元素?
如何检测?
猜测:ArrowXml
旨在将 XML 树作为输入并生成 XML 树作为输出。 hasName
查询输入; eelem
修改输出。我会很惊讶地发现有一种方法可以查询输出或修改输入。
另一方面,arrows
软件包似乎提供了一个可能有用的 stateful arrow transformer。大概你可以写一个 instance ArrowXml a => ArrowXml (StateArrow s a)
.
这是类型签名真正有用的情况之一。盯着类型签名看一秒钟:
(+=) :: (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
s 转换为 XmlTree
s 并将 "merging them together" 转换为一台机器,该机器接收 b
并排出 XmlTree
。但重要的是,这个类型签名告诉我们,或多或少 only 可以实现的方式是:
arr1 += arr2 = arr f . (arr1 &&& arr2) where
f :: (XmlTree, XmlTree) -> XmlTree
f = _
这是因为"free theorems";如果您不知道参数的类型,那么我们可以证明您真的无法做很多事情。 (它可能会更复杂一些,因为箭头可能具有未完全由 arr
封装的结构,例如内部计数器与 .
相加,然后设置为 0
当使用 arr
时。所以实际上用通用的 a (XmlTree, XmlTree) XmlTree
替换 arr f
就可以了。)
所以我们必须并行执行这里的两个箭头。这就是我想说的。因为组合器 (+=)
不知道 b
是什么,所以它别无选择,只能愉快地将 b
并行输入箭头,然后尝试将它们的输出组合在一起。所以,deep (hasName "bar")
不看 foo
.
如果你真的愿意,你可以用 deep (hasName "bar") . foo
做一个相互递归的解决方案,但它似乎有潜在的危险(即无限循环),所以简单地定义这样的东西可能更安全:
a ++= b = a += (b . a)
其中 "current" a 是 "fed" 到 b 以产生更新。为此,您必须从 Control.Category
导入 .
,因为它与 Prelude..
(仅执行函数组合)不同。这看起来像:
import Prelude hiding ((.))
import Control.Category ((.))
这是有效的解决方案,感谢@ChrisDrost:
infixl 7 ++=
(++=) :: ArrowXml a => a XmlTree XmlTree -> a XmlTree XmlTree ->
a XmlTree XmlTree
a ++= b = a += (a >>> b)
在我的理解中,+=
只是传递它的输入,因为它的类型是 b
,
所以它无论如何也做不了多少。所以,它只是通过链
运营商不变。在这里,我们对输入施加了更强的约束
++=
可以是:必须是XmlArrow
。我们把这个箭头送到第二个
++=
的参数,因此它知道 XML 文档的 «updated» 状态。
正如我们从 我之前学到的
问题,
+=
是允许一次添加一个元素的运算符。是否有可能
«检测»先前添加的元素并控制未来添加的方式
完成了吗?
这里有一个简单的程序来开始我们的调查:
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'元素?如何检测?
猜测:ArrowXml
旨在将 XML 树作为输入并生成 XML 树作为输出。 hasName
查询输入; eelem
修改输出。我会很惊讶地发现有一种方法可以查询输出或修改输入。
另一方面,arrows
软件包似乎提供了一个可能有用的 stateful arrow transformer。大概你可以写一个 instance ArrowXml a => ArrowXml (StateArrow s a)
.
这是类型签名真正有用的情况之一。盯着类型签名看一秒钟:
(+=) :: (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
s 转换为 XmlTree
s 并将 "merging them together" 转换为一台机器,该机器接收 b
并排出 XmlTree
。但重要的是,这个类型签名告诉我们,或多或少 only 可以实现的方式是:
arr1 += arr2 = arr f . (arr1 &&& arr2) where
f :: (XmlTree, XmlTree) -> XmlTree
f = _
这是因为"free theorems";如果您不知道参数的类型,那么我们可以证明您真的无法做很多事情。 (它可能会更复杂一些,因为箭头可能具有未完全由 arr
封装的结构,例如内部计数器与 .
相加,然后设置为 0
当使用 arr
时。所以实际上用通用的 a (XmlTree, XmlTree) XmlTree
替换 arr f
就可以了。)
所以我们必须并行执行这里的两个箭头。这就是我想说的。因为组合器 (+=)
不知道 b
是什么,所以它别无选择,只能愉快地将 b
并行输入箭头,然后尝试将它们的输出组合在一起。所以,deep (hasName "bar")
不看 foo
.
如果你真的愿意,你可以用 deep (hasName "bar") . foo
做一个相互递归的解决方案,但它似乎有潜在的危险(即无限循环),所以简单地定义这样的东西可能更安全:
a ++= b = a += (b . a)
其中 "current" a 是 "fed" 到 b 以产生更新。为此,您必须从 Control.Category
导入 .
,因为它与 Prelude..
(仅执行函数组合)不同。这看起来像:
import Prelude hiding ((.))
import Control.Category ((.))
这是有效的解决方案,感谢@ChrisDrost:
infixl 7 ++=
(++=) :: ArrowXml a => a XmlTree XmlTree -> a XmlTree XmlTree ->
a XmlTree XmlTree
a ++= b = a += (a >>> b)
在我的理解中,+=
只是传递它的输入,因为它的类型是 b
,
所以它无论如何也做不了多少。所以,它只是通过链
运营商不变。在这里,我们对输入施加了更强的约束
++=
可以是:必须是XmlArrow
。我们把这个箭头送到第二个
++=
的参数,因此它知道 XML 文档的 «updated» 状态。