问题
This question is about HXT, but I guess it's applicable to conception of
ArrowPlus
in general. Consider the following program:
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"
Can you tell what will be saved in test.xml
? My expectations:
<?xml version="1.0" encoding="UTF-8"?>
<foo>
<bar/>
<bar/>
<bar/>
</foo>
My logic: arrow bar
copies all its input and adds one ‘bar’ element
(this
is alias for identity arrow):
| |
this eelem "bar"
| |
\ /
\ /
<+>
|
So, result of bar >>> bar >>> bar
should be three ‘bar’ elements (note
that eelem "bar" >>> eelem "bar"
will result in only one ‘bar’ element,
since arrows of mkelem
family ignore their input (although it still can be
used to generate their contents) and output only newly created element).
Having said all that, I present contents of test.xml
after execution of
the program:
<?xml version="1.0" encoding="UTF-8"?>
<foo>
<//>
<bar/>
<bar/>
<bar/>
<bar/>
<bar/>
<bar/>
<bar/>
</foo>
Questions:
What is
<//>
?Why there are 7 ‘bar’ elements instead of 3? What is reason of this duplication?
Why when I replace
bar >>> bar >>> bar
withnone >>> bar >>> bar >>> bar
I get:
<?xml version="1.0" encoding="UTF-8"?>
<foo/>
where none
is zero arrow. We deal with monoid on arrows here, right?
none
(≡ zeroArrow
) should be identity for it, so it should go like:
none <+> eelem "bar"
which produces a ‘bar’ element and subsequent
calls should add two another elements. But we don't get any!
- How to write proper version of
bar
arrow that would add one ‘bar’ element at a time?
Sorry for asking 4 questions, but I guess they are closely related, so it shouldn't be a problem.
回答1:
It seems like you have some confusion between how the >>>
and <+>
operators work. To build intuition, let's first define two different bar
s:
bar1 :: ArrowXml a => a XmlTree XmlTree
bar1 = this <+> eelem "bar"
bar2 :: ArrowXml a => a n XmlTree
bar2 = eelem "bar"
The first thing we notice is the type signature. bar1
has an input type of XmlTree
, meaning that it modifies an existing tree in some way, whereas bar2
discards its argument. This is due to the use of this
in bar1
that copies its elements. Now, let's load these up in ghci
to figure out how >>>
and <+>
work together:
Prelude Text.XML.HXT.Core> runX $ xshow $ bar2
["<bar/>"]
Prelude Text.XML.HXT.Core> runX $ xshow $ bar2 >>> bar2 >>> bar2
["<bar/>"]
Hm, that's weird, it keeps creating the same structure no matter how many times we compose it with >>>
. This happens because for bar2
, we're discarding the tree each time we transform it: remember it's type signature is a n XmlTree
instead of a XmlTree XmlTree
. Let's compare that to 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/>"]
Woah, it's growing exponentially! Why? Well, each time you compose with >>>
, you're taking the previous tree, and for each element applying the function application this <+> eelem "bar"
. The first invocation of bar1
has no previous tree, so this
becomes the root node and you simply append an element of <bar/>
to it. However, for bar1 >>> bar1
, the first bar1
will create <//><bar/>
and the second one will compose each node of <//><bar/>
with bar1
again, leading to:
bar1 === <//><bar/>
bar1 >>> bar1 === <//><bar/><bar/><bar/>
|--------||----------|
First Second
Now you keep doing that, and you can see how bar1 >>> bar1 >>> bar1
will produce seven <bar/>
s preceded by a <//>
.
OK, so now that we have intuition for >>>
in terms of ArrowXml
we can see how <+>
behaves:
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/>"]
Oh, that's pretty simple... they just append one after the other. We can see that the type of bar1 <+> bar1
is still one that transforms values of type a XmlTree XmlTree
, so you will get some crazy behavior if you combine it with >>>
. See if you can wrap your mind around this output:
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/>"]
To answer your question about none
, check it's type signature:
Prelude Text.XML.HXT.Core> :t none
none :: ArrowList a => a b c
To me that says it takes a value of type b
and returns a value of type c
. Since we have no idea what c
is (we didn't provide it as an argument to none
), we can assume that it's going to be the empty set. This makes sense with our previous definitions of >>>
and <+>
:
Prelude Text.XML.HXT.Core> runX $ xshow $ none <+> bar1
["<//><bar/>"]
Prelude Text.XML.HXT.Core> runX $ xshow $ none >>> bar1
[""]
In the first case, the empty document appended with another document is basically the identity operation. In the second one, none
produces no elements, so when you compose it with bar1
, there are no elements to operate on, and hence the result is the empty document. In fact, since Haskell is lazy we can be even more cavalier about what we compose with none
since we know that it'll never be evaluated:
Prelude Text.XML.HXT.Core> runX $ xshow $ none >>> undefined
[""]
Given this knowledge, what you were probably trying to do was something like this:
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>"]
EDIT
A similar solution is to use the +=
operator:
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>"]
来源:https://stackoverflow.com/questions/30029001/arrow-to-add-one-element-at-a-time