2
votes

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:

  1. What is <//>?

  2. Why there are 7 ‘bar’ elements instead of 3? What is reason of this duplication?

  3. Why when I replace bar >>> bar >>> bar with none >>> 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!

  1. 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

1 Answers

2
votes

It seems like you have some confusion between how the >>> and <+> operators work. To build intuition, let's first define two different bars:

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>"]