Dealing with .NET generic dictionaries in F#?
f#
Solution
First, your immediate problem: the way you defined the `Atts` property, it's not one value that is "stored" somewhere and is accessible via property. Instead, your definition means "every time somebody reads this property, create a new dictionary and return it". This is why your new attribute doesn't appear in the dictionary: it's a different dictionary every time you read `root.Atts`.
To create a property with a backing field and initial value, use `member val`:
type XmlNode(...) =
...
member val Atts = Dictionary<string,object>()
Now, answers to some implied questions.
First order of business: "modify the attributes" and "purely functional" are contradictory ideas. Functional programming implies immutable data. Nothing changes ever. The way to advance your computation is to create a new datum at every step, without overwriting the previous one. This basic idea turns out to be immensely valuable in practice: safer threading, trivial "undo" scenarios, trivial parallelization, trivial distribution to other machines, and even reduced memory consumption via persistent data structures.
Immutability is a very important point, and I urge you not to glance over it. Accepting it requires a mental shift. From my own (and other people I know) experience, it is very hard coming from imperative programming, but it is well worth it.
Second: do not use classes and properties. Technically speaking, object-oriented programming (in the sense of message passing) is not contradictory to functional, but the Enterprise flavor that is used in practice and implemented in C++, Java, C# et al., is contradictory, because it emphasizes this idea that "methods are operations that change an object's state", which is not functional (see above). So it's better to avoid object-oriented constructs, at least while you're learning. And especially since F# provides much better ways to encode data:
type XmlNode = { TagName: string; InnerValue: string; Atts: (string*string) list }
(notice how my `Atts` is not a dictionary; we'll come to this in a bit)
Similarly, to represent operations on your data, use functions, not methods:
let printNode (node: XmlNode) = (* we'll come to the implementation later *)
Third: why do you say that you "obviously" need to modify the attributes? The code you've shown does not call for this. For example, using my definition of `XmlNode` above, I can rewrite your code this way:
[<EntryPoint>]
let main argv =
let root = { TagName = "root"; InnerValue = "test"; Atts = ["att", "val"] }
printfn "%s" (printNode root)
...
But even if that was a real need, you shouldn't do it "in place". As I've described above while talking about immutability, you should not mutate the existing node, but rather create a new node that differs from the original one in whatever way you wanted to "modify":
let addAttr node name value = { node with Atts = (name, value) :: node.Atts }
In this implementation, I take a node and name/value of an attribute, and produce a new node whose `Atts` list consists of whatever was in the original node's `Atts` with the new attribute prepended.
The original `Atts` list stays intact, unmodified. But this does not mean twice the memory consumption: because we know that the original list never changes, we can reuse it: we create the new list by only allocating memory for the new item and including a reference to the old list as "other items". If the old list was subject to change, we couldn't do that, we would have to create a full copy (see "Defensive Copy"). This strategy is known as "Persistent Data Structure". It is one of the pillars of functional programming.
Finally, for string formatting, I recommend using `sprintf` instead of `StringBuilder`. It offers similar performance benefits, but in addition provides type safety. For example, code `sprintf "%s" 5` will not compile, complaining that the format expects a string, but the final argument `5` is a number. With this, we can implement the `printNode` function:
let printNode (node: XmlNode) =
let atts = seq { for n, v in node.Atts -> sprintf " %s=\"%s\"" n v } |> String.concat ""
sprintf "<%s%s>%s</%s>" node.TagName atts node.InnerValue node.TagName
For reference, here's your complete program, rewritten in functional style:
type XmlNode = { TagName: string; InnerValue: string; Atts: (string*string) list }
let printNode (node: XmlNode) =
let atts = seq { for n, v in node.Atts -> sprintf " %s=\"%s\"" n v } |> String.concat ""
sprintf "<%s%s>%s</%s>" node.TagName atts node.InnerValue node.TagName
[<EntryPoint>]
let main argv =
let root = { TagName = "root"; InnerValue = "test"; Atts = ["att", "val"] }
printfn "%s" (printNode root)
Console.ReadLine() |> ignore
0
Problem
- I am not a functional programmer. - I am learning F#. - I got a problem here. Let me start from following piece of code: ``` type XmlNode(tagName, innerValue) = member this.TagName = tagName member this.InnerValue = innerValue member this.Atts = Dictionary<string, obj>() ``` I don't use F# `dict` because (as I know) that one is readonly, however I obviously need to modify my attributes. So I am really struggling to make it pure functional way: ``` type XmlNode with member this.WriteTo (output:StringBuilder) = output.Append("<" + this.TagName) |> ignore //let writeAtts = // List.map2 (fun key value -> " " + key + "=" + value.ToString()) (List.ofSeq this.Atts.Keys) (List.ofSeq this.Atts.Values) // |> List.reduce (fun acc str -> acc + " " + str) //output.Append((writeAtts)) |> ignore output.Append(">" + this.InnerValue + "</" + this.TagName + ">") |> ignore output ``` The code I commented out was my (probably stupid) attemp to use mapping and reduction to concat all the atts in the single correctly formatted string. And that compiles OK. But when I try to access my Atts property: ``` [<EntryPoint>] let main argv = let root = new XmlNode("root", "test") root.Atts.Add("att", "val") // trying to add a new KVP let output = new StringBuilder() printfn "%O" (root.WriteTo(output)) Console.ReadLine()|>ignore 0 // return an integer exit code ``` ...new attribute does not appear inside the Atts property, i.e. it remains empty. So: 1) help me to make my code more functional. 2) and to understand how to deal with modificable dictionaries in F#. Thank you.