Functional Programming in .NET – Adding Extensibility

Thanks for everyone who attended my session on applied functional programming earlier this week at RockNUG.  This session was intended to reinforce the basics of thinking functionally and what techniques you can do right now to take advantage.  It was more of a subset of my workshop I gave at the Continuous Improvement in Software Conference last year, with the addition of a few items.  Of course I had to through in some Haskell as well and show off the Jolt Award winning book Real World Haskell.

My previous post on the essay by Bertrand Meyer on Beautiful Architecture – Functional Programming versus Object Oriented Programming gave me some inspiration to revisit some of my Functional C# programming I had done in the past as well as the Functional C# library.  There are some very interesting ideas on how we can mix and match paradigms to create even more extensible and powerful software.  Instead of squabbling about which is a more extensible paradigm, let’s walk through some ideas.

Extensibility Through Closures

One of the first areas that I covered was extensibility through the use of closures.  For example, we  can expose a certain aspect of our API for configuration while the implementing function handles the boilerplate around it, which could include managing resources, object lifetimes, etc.   To make this a little more concrete, let’s walk through an example.   Say we want to execute a system process with some given configuration information.  The underlying function will create the process with the configuration, wait for the exit and return us an error code.  In order to configure this process, we can expose the configuration through a closure in which we are free to set any number of properties.  Below is the implementation as written in F#.

open System.Diagnostics
let execProcess (infoAction : ProcessStartInfo -> unit) : int =
  use p = new Process()
  infoAction p.StartInfo
  p.Start() |> ignore
  p.WaitForExit()
  p.ExitCode

This is a fairly straight forward implementation to create a process, execute the closure with the configuration information, start the process, wait for the exit and return us the error code.  Then, we’re able to run simple processes such as the following:

let code = 
  execProcess (fun info ->  
    info.FileName <- "xcopy.exe"  
    info.Arguments <- "/E dir1 dir2")

With very little effort, we’re able to launch a process, have its lifetime managed, all through the use of a closure as our extensibility point.  This is a pretty basic example, so let’s get a little more advanced.  There are tools out there designed with some of these ideas in mind already.

With the release of Jeremy Miller’s 2.5 release of StructureMap, there has been a lot of very interesting and innovative work when it comes to registration and the use of closures as an extensibility model.  This allows us not only to add registrations of types, but also for interception capabilities as well.

Let’s walk through a simple example of creating the trite example of a logger and using the container registration closure to register our preferences.  Below is how I might use this in F# to both register and get an instance of my logger class.

#light

open System
open StructureMap
open Xunit

type ILogger =
  abstract member Log : string -> unit

type ConsoleLogger() =
  interface ILogger with 
    member x.Log msg = Console.WriteLine msg

[<Fact>]
let should_register_type() =
  let container = 
    new Container(Action<ConfigurationExpression>(
                    fun x -> 
                      (x.ForRequestedType<ILogger>())
                        .TheDefaultIsConcreteType<ConsoleLogger>() 
                        |> ignore))
                      
  let logger = container.GetInstance<ILogger>()                      
  Assert.NotNull logger

The code above is rather straight forward with the use of an Action<ConfigurationExpression> to register my preferences.  As F# is a more strongly typed language than C#, it will complain if my last statement returns a value when the function signature says it will not, hence the use of the ignore function.  And of course the test succeeds as expected.

Overall, this approach works very well and I’ve been impressed with a lot of the work going on with the project.  As F# is a multi-paradigm language, IoC and other object-oriented concerns can be implemented in the language, and indeed if interoperating with other languages, it’s a good idea from the perspective of how we package our code to be consumed.  With the extensibility of the F# syntax, we could be a little more flexible in the use of language oriented programming techniques for registration, but that’s for another time.

Conclusion

This is just one of the examples that speaks to the power of functional techniques inside of the languages we already know.  There are other examples that we could cover as well in the next couple of posts which include lazy evaluation, the use of expressions as a way to rid ourselves of magic strings, as well as other ideas.

No Comments