3

I have the following code to deal several different xml files (different scheme) which all have node of <parameters>. Now I found the match part will be used in many place so I want to create a function for it.

let xml1 = XmlProvider<"./file1.xml">.Parse(resp) 
match xml1.Parameters |> Seq.tryFind (fun x -> x.Name = "token")  with
| Some value -> value.Value |> Some 
| None -> None

let xml2 = XmlProvider<"./file2.xml">.Parse(resp) // different scheme but has <parameters>
match xml2.Parameters |> Seq.tryFind (fun x -> x.Name = "token")  with
| Some value -> value.Value |> Some 
| None -> None

// more very different files but has <parameters> ......

In the above example you can see the match part is repeated. How to define the function?

let getToken (parameters : <what the type?>) = 
    match parameters |> Seq.tryFind (fun x -> x.Name = "token")  with
    | Some value -> value.Value |> Some 
    | None -> None

What the type of parameters should be?

Update: I've updated the question. BTW, is this a case to show the usefulness of Structural typing?

Here is the code (not workable yet) for testing. xml1 and xml2 have totally different xml scheme except they both have the <parameters> part.

let input1 = """<r1><parameters><parameter name="token">1</parameter><parameter name="other">xxx</parameter></parameters><othersOf1>..sample....</othersOf1></r1>"""
let xml1 = XmlProvider<"""<r1><parameters><parameter name="token">1</parameter><parameter name="other">xxx</parameter></parameters><othersOf1>...</othersOf1></r1>""">.Parse(input1)

let input2 = """<r2><parameters><parameter name="token">1</parameter><parameter name="other">xxx</parameter></parameters><othersOf2>...sample...</othersOf2></r2>"""
let xml2 = XmlProvider<"""<r2><parameters><parameter name="token">1</parameter><parameter name="other">xxx</parameter></parameters><othersOf2>...</othersOf2></r2>""">.Parse(input2)

let getToken (parameters: ????) =
    match parameters |> Seq.tryFind (fun x -> x.Name = "token")  with
    | Some value -> value.Value |> Some 
    | None -> None

let token1 = getToken xml1.Parameters
let token2 = getToken xml2.Parameters
ca9163d9
  • 27,283
  • 64
  • 210
  • 413

2 Answers2

3

The idea is to have a function that takes a seq of some type ^P which has Name and Value. For educational purposes Name can be of any type 'a (supporting equality) and Value is of generic type 'b:

let inline get name parameters =
    parameters |> Seq.tryFind (fun x -> (^P : (member Name : 'a) x) = name)
    |> Option.map (fun v -> (^P : (member Value : 'b) v))

Adding some values <parameter name="token" value="123"> to XML1 and changing name to an int as well having a DateTime value in XML2 <parameter name="12" value="2016-12-31"> allows to get:

let token1 = get "token" xml1.Parameters // : int option
let token2 = get 12 xml2.Parameters // : DateTime option

So, yes, you are right, this can be done leveraging structural typing.

The actual type of parameters depends on name as well. The full type of get is:

name:'a -> parameters:seq< ^P> -> 'b option
  when 'a : equality and  ^P : (member get_Name :  ^P -> 'a) and
        ^P : (member get_Value :  ^P -> 'b)
CaringDev
  • 8,391
  • 1
  • 24
  • 43
  • Is the inline/Statically Resolved Type the same powerful as structural typing? How they compare? – ca9163d9 Jul 12 '16 at 18:02
  • @dc7a9163d9 Statically resolved type parameters are as far as you can get with structural typing in F#, because of F# being subject to the nominal .NET type system. Compared to e.g. OCaml this is less powerful due to (mainly, at least) the following aspects: - OCaml is structural typed first - In F# you can't declare a structural type or an alias, e.g. `type P<'a> = (^P when ^P : (member Name : 'a))` for reuse or declare an interface (e.g. in TypeScript) from which all compatible classes will automatically 'inherit'. – CaringDev Jul 12 '16 at 20:00
  • @dc7a9163d9 See also http://stackoverflow.com/questions/3162387/why-is-fs-type-inference-so-fickle/3162832#3162832, https://fslang.uservoice.com/forums/245727-f-language/suggestions/6181848-provide-better-support-for-structural-typing https://fslang.uservoice.com/forums/245727-f-language/suggestions/6181848-provide-better-support-for-structural-typing https://fslang.uservoice.com/forums/245727-f-language/suggestions/9633858-structural-extensible-records-like-elm-concrete https://en.wikipedia.org/wiki/Structural_type_system (OCaml example) – CaringDev Jul 12 '16 at 20:02
  • Thanks very much. I tested the code but I got an error so I posted another question http://stackoverflow.com/questions/38337854/the-type-xmlprovider-parameter-does-not-support-the-operator-get-value. Do you know why the error occurs? There should be `Value` property. – ca9163d9 Jul 12 '16 at 20:04
2

Generally speaking, the types of nested nodes become nested types of the type provided by the XML type provider.

To access the types, you'll first need to use a type alias and name your provided type:

type DbToken = XmlProvider<"""<parameters>
    <parameter name="token" value="123" />
    <parameter name="token" value="123" /> 
  </parameters>""">

I'm using an inline XML example, so that I can test it, but it should work the same with a file.

The type of single <parameter /> node in the file is now DbToken.Parameter (name is auto-generated to be unique and may vary), so we can now write a function:

let getToken (parameters:DbToken.Parameter[]) = 
    match parameters |> Seq.tryFind (fun x -> x.Name = "token")  with
    | Some value -> value.Value |> Some 
    | None -> None

And the following call works:

let xml = DbToken.GetSample()    
getToken xml.Parameters
Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
  • Thanks. I've updated the question. The xml files have different scheme but they may all have the node of ``. – ca9163d9 Jul 08 '16 at 18:56
  • 1
    If you invoke `XmlProvider` twice, you'll get two different types - there is not much you can do about that. An alternative is to use `SampleIsList` parameter (http://fsharp.github.io/FSharp.Data/reference/fsharp-data-xmlprovider.html) to specify multiple different examples to the provider - then the types will be shared (if you also set `Global=true`, the provider will unify types based on tag names). – Tomas Petricek Jul 08 '16 at 21:35