I am currently learning F# and functional programming in general (from a C# background) and I have a question about using .net CLR objects during my processing.
The best way to describe my problem will be to give an example:
let xml = new XmlDocument()
|> fun doc -> doc.Load("report.xml"); doc
let xsl = new XslCompiledTransform()
|> fun doc -> doc.Load("report.xsl"); doc
let transformedXml =
new MemoryStream()
|> fun mem -> xsl.Transform(xml.CreateNavigator(), null, mem); mem
This code transforms an XML document with an XSLT document using .net objects. Note XslCompiledTransform.Load works on an object, and returns void. Also the XslCompiledTransform.Transform requires a memorystream object and returns void.
The above strategy used is to add the object at the end (the ; mem) to return a value and make functional programming work.
When we want to do this one after another we have a function on each line with a return value at the end:
let myFunc =
new XmlDocument("doc")
|> fun a -> a.Load("report.xml"); a
|> fun a -> a.AppendChild(new XmlElement("Happy")); a
Is there a more correct way (in terms of functional programming) to handle .net objects and objects that were created in a more OO environment?
The way I returned the value at the end then had inline functions everywhere feels a bit like a hack and not the correct way to do this.
Any help is greatly appreciated!
One of the great benefits of F# is that it allows you to mix the functional programming style with other styles (namely object-oriented and imperative). Since most of the .NET libraries are object-oriented and imperative, the best way to access .NET functionality from F# is to simply use the imperative features of F#. This means that when working with .NET objects, the idiomatical F# code will look almost like C#.
EDIT: The following slightly modified example shows how to wrap XSL transformation into a function that takes the name of the input file and a name of the xsl file. It returns the MemoryStream
where the output was written:
let transformDocument inputFile xslFile =
let doc = new XmlDocument()
doc.Load(inputFile)
let xsl = new XslCompiledTransform()
xsl.Load(xslFile)
let mem = new MemoryStream()
xsl.Transform(xml.CreateNavigator(), null, mem)
mem
And the second example:
let doc = new XmlDocument("doc")
doc.Load("report.xml")
doc.AppendNode(new XmlElement("Happy"))
This doesn't mean that you're turning away from the functional style in any way - there are many opportunities to use functional style when working with .NET classes. For example you can use higher-order functions like Seq.filter
and Seq.map
(or sequence expressions) to process collections of data (or XML elements). You can still write abstractions using higher-order functions.
The System.Xml
namespace is very imperative, so there isn't much space for functional style. However, the code that generates the data that you store in XML can be fully functional. It may be worth looking at the LINQ to XML classes (in .NET 3.5+), because they are designed in a much more functional-programming-friendly fashion (as they are supposed to work well with LINQ, which is also functional).
To add another nifty tip to Tomas already excellent answer, is the opportunity to curry these functions to give a good indication of how useful F# is, even using imperative coding techniques when required.
The example 1 Tomas used was to transform an xml document given an xsl document:
let transformDocument inputFile xslFile =
let doc = new XmlDocument()
doc.Load(inputFile)
let xsl = new XslCompiledTransform()
xsl.Load(xslFile)
let mem = new MemoryStream()
xsl.Transform(xml.CreateNavigator(), null, mem)
mem
After reading this post, I thought of a way to go one step further and allow us to curry this function. What this means is (if we choose) we can pass the function only an XSL document, it will return a function that will transform any xml document given the specified XSL document:
let transform =
(fun xsl ->
let xsl_doc = new XslCompiledTransform()
xsl_doc.Load(string xsl)
(fun xml ->
let doc = new XmlDocument()
doc.Load(string xml)
let mem = new MemoryStream()
xsl_doc.Transform(doc.CreateNavigator(), null, mem)
mem
)
)
So we could do the following:
let transform_report_xsl = transform "report.xsl"
let transform_style_xsl = transform "style.xsl"
transform_report_xsl "report.xml"
transform_report_xsl "report2.xml"
transform_style_xsl "page.xml"
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With