Search code examples
haskellfunctorapplicativehxtarrow-abstraction

how to mix applicative functors and arrows


i read on Andrew Birkett’s blog Applicative arrows for XML &&& return to pure that we could mix arrows and applicative functors.

I tried it by my own but i don't have what i expect. i would like this result:

[Scenario {scenario = "11111", origin = "333", alarm = "Sonde1"},
 Scenario {scenario = "22222", origin = "444", alarm = "Sonde2"}]

but i get this instead:

[Scenario {scenario = "11111", origin = "333", alarm = "Sonde1"},
 Scenario {scenario = "11111", origin = "333", alarm = "Sonde2"},
 Scenario {scenario = "11111", origin = "444", alarm = "Sonde1"},
 Scenario {scenario = "11111", origin = "444", alarm = "Sonde2"},
 Scenario {scenario = "22222", origin = "333", alarm = "Sonde1"},
 Scenario {scenario = "22222", origin = "333", alarm = "Sonde2"},
 Scenario {scenario = "22222", origin = "444", alarm = "Sonde1"},
 Scenario {scenario = "22222", origin = "444", alarm = "Sonde2"}]

i think there is a twist in my code but i don't know where to search.

Below is my code if anyone can suggest some help.

{-# LANGUAGE Arrows, NoMonomorphismRestriction #-}

import Text.XML.HXT.Core
import Control.Applicative
import Text.XML.HXT.Arrow.ReadDocument
import Data.Maybe
import Text.XML.HXT.XPath.Arrows
import Text.Printf


data Scenario = Scenario
  { scenario, origin, alarm    :: String
  }
  deriving (Show, Eq)


xml= "<DATAS LANG='en'>\
    \ <SCENARIO ID='11111'>\
    \   <ORIGIN ID='333'>\
    \       <SCENARIO_S ERR='0'></SCENARIO_S>\
    \       <SCENARIO_S ERR='2'></SCENARIO_S>\
    \       <ALARM_M NAME='Sonde1'></ALARM_M>\
    \   </ORIGIN>\
    \ </SCENARIO>\
    \ <SCENARIO ID='22222'>\
    \   <ORIGIN ID='444'>\
    \       <SCENARIO_S ERR='10'></SCENARIO_S>\
    \       <SCENARIO_S ERR='12'></SCENARIO_S>\
    \       <ALARM_M NAME='Sonde2'></ALARM_M>\
    \   </ORIGIN>\
    \ </SCENARIO>\
    \</DATAS>"

parseXML string = readString [ withValidate no
                         , withRemoveWS yes  -- throw away formating WS
                         ] string


parseVal tag name = WrapArrow $ getXPathTrees (printf "/DATAS/%s" tag) >>>  getAttrValue name

parseDatas = unwrapArrow $ Scenario <$> parseVal "SCENARIO"      "ID"
                                 <*> parseVal "SCENARIO/ORIGIN"        "ID"
                                 <*> parseVal "SCENARIO/ORIGIN/ALARM_M"        "NAME"

testarr1= runX (parseXML xml >>> parseDatas)

Solution

  • As pointed out by rampion, the problem is how the list monad works with applicative. Take a look at this:

    λ *Main > (+) <$> [1,2,3] <*> [1,2,3]
    [2,3,4,3,4,5,4,5,6]
    

    The result is the carthesian product of (+) applied to [1,2,3] and [1,2,3]: the result list has 9 elements.

    In your code, parseVal "SCENARIO" "ID" will return a list of 2 elements, and so will parseVal "SCENARIO/ORIGIN" "ID" and parseVal "SCENARIO/ORIGIN/ALARM_M" "NAME". Therefore, the result will have 8 elements.

    Instead, this is how I would change your code:

    --- parse a generic tag
    parseVal tag name = WrapArrow $ getXPathTrees (printf "%s" tag) >>>  getAttrValue name
    
    --- parse a "SCENARIO" xml element
    parseScenario = unwrapArrow $ Scenario
            <$> (WrapArrow $ getAttrValue "ID")
            <*> (parseVal "SCENARIO/ORIGIN" "ID")
            <*> (parseVal "SCENARIO/ORIGIN/ALARM_M" "NAME")
    
    --- parse the XML, extract a list of SCENARIOS and, for each, apply parseScenario
    testarr1= runX (parseXML xml >>> getXPathTrees (printf "/DATAS/SCENARIO" ) >>> parseScenario)
    

    The result is as desired:

    λ *Main > testarr1 
    [Scenario {scenario = "11111", origin = "333", alarm = "Sonde1"},Scenario {scenario = "22222", origin = "444", alarm = "Sonde2"}]