Question

I am trying to parse a directory of XML files and then select the value of particular attribute if a given node is present. Am not able to understand the reason for the compilation error that the following F# is causing.

open System
open System.IO
open System.Xml
open System.Xml.XPath
open System.Xml.Linq


let configRootDirectory = @"C:\dir"
let relativeProductDir = @"relDir"

let ExtractConfiguredCalculator (productConfigFile:string) = 
    let xmlNavigator = XPathDocument(productConfigFile).CreateNavigator()
    let node = xmlNavigator.SelectSingleNode(@"Product/SupportedRisk/Risk[@type='PV']") 
    node.GetAttribute("methodology", "")

let configFile = Directory.GetFiles(Path.Combine(configRootDirectory, relativeProductDir), @"*.xml")
                    |> Seq.cast<string>
                    |> Seq.iter(fun configFileName -> ExtractConfiguredCalculator(configFileName))                  
                    |> Seq.filter(fun configuredCalculatorNode -> configuredCalculatorNode != null)
                    |> Seq.iter(fun calculator -> Console.WriteLine(calculator))

The above snippet is from the code that I am experimenting with in LinqPad. The error message seen is as below.

This expression was expected to have type     unit     but here has type     string   

Update Trying to get more f#-ish. Please suggest if something can be improved.

let configFile = 
        Directory.GetFiles(Path.Combine(configRootDirectory, relativeProductDir), @"*.xml")    
        |> Seq.map(fun configFileName -> 
                    let xmlNavigator = XPathDocument(configFileName).CreateNavigator()
                    let node = xmlNavigator.SelectSingleNode(@"Product/SupportedRisk/Risk[@type='PV']")
                    match node with
                    | null -> "PV not configured"
                    | _ -> 
                        let attributeValue = node.GetAttribute("methodology", "")
                        match attributeValue with 
                        | null -> "Calculator not configured"
                        | _ -> attributeValue)
        |> Seq.iter (printfn "%s")
Was it helpful?

Solution

You have to change the first Seq.iter to Seq.map to return a sequence which is required by the subsequent Seq.filter.

I have several comments though:

  • Seq.cast is redundant since Directory.GetFiles returns string [].
  • When you have Seq.map and Seq.filter together, you could always replace them by Seq.choose.
  • printfn is a more F#ish way of printing than Console.WriteLine.

Here is an improved version:

let configFile = 
        Directory.GetFiles(Path.Combine(configRootDirectory, relativeProductDir), @"*.xml")    
        |> Seq.choose (fun configFileName -> 
                            let config = ExtractConfiguredCalculator(configFileName)
                            if config <> null then Some config else None)
        |> Seq.iter (printfn "%s")

OTHER TIPS

Your first call to Seq.iter should be to Seq.map. ExtractConfiguredCalculator returns string but Seq.iter expects a function returning unit.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top