Object expressions in F#

Today I’ve been building a simple auditing feature for LightSpeed which automatically logs who created, deleted or last modified an entity. One of the wrinkles in this feature is of course that the way to get the username differs from application to application: in a Windows client application, it might be the logged-on user, but in a Web application with Forms authentication we’d want it to be the HttpContext user rather than the operating system user account. Obviously, this is a great fit for the strategy pattern, and that’s exactly what I used: I defined an interface for getting the current user, and built Windows client and ASP.NET implementations of that interface.

internal class WindowsIdentityAuditInfoStrategy : IAuditInfoStrategy
{
  public static readonly IAuditInfoStrategy Instance = new WindowsIdentityAuditInfoStrategy();
  private WindowsIdentityAuditInfoStrategy() { }
  public AuditInfoMode Mode { get { return AuditInfoMode.WindowsIdentity; } }
  public string GetCurrentUser() { /* super-secret Mindscape proprietary code */ }
}

The annoying thing about this is that I don’t really need all the overhead of a class, and could also do without the trickery required to make it a singleton. What I’d really like to do is declare an object that implements the interface. That would save me writing all the class verbiage, and also make it clearer to readers of my code that the object was a singleton.

If I’d been using F#, I could have done exactly this. F# has a feature called object expressions, which basically allow you to create an object with members and behaviour, without creating a class to contain those members and behaviour. Let’s look at an example.

let windowsIdentityAuditInfoStrategy = {
  new IAuditInfoStrategy with
    member this.Mode = AuditInfoMode.WindowsIdentity
    member this.GetCurrentUser() = WindowsIdentity.GetCurrent().Name }  // oh no!  I gave it away!
 
printfn "%A" (windowsIdentityAuditInfoStrategy.GetCurrentUser())
// prints "athena\Ivan"

What’s going on here? That opening brace followed by new IAuditInfoStrategy tells F# that this is an object expression: that I want an object that implements IAuditInfoStrategy as shown. F# obligingly creates a suitable object by some nefarious means which I don’t have to worry about, and returns it into my variable.

Object expressions aren’t limited to interfaces. You can use them whenever you want to inject a bit of implementation without creating a whole new type:

let purportedDromedary = {
  new Object() with
    member this.ToString() = "I'm a dromedary" }
 
printfn "%A" purportedDromedary
// prints I'm a dromedary

Nor are they limited to singletons. In fact a handy feature of F# object expressions is that they can capture local variables, just like closures do:

let purported thingy = { new Object() with member this.ToString() = "I'm a " + thingy }
 
printfn "%A" (purported "wildebeeste")
// prints I'm a wildebeeste
printfn "%A" (purported "trask")
// prints I'm a trask

Obviously impersonating equatorial wildlife is fairly specialised as business requirements go, so let’s go back to the auditing example. In LightSpeed, the IAuditInfoStrategy is public, so that customers can implement their own application-specific user identification strategies. Perhaps the application has its own custom security system. Perhaps it is integrated into Lotus Notes– but no, some things are too horrible to contemplate. Regardless, we might find it useful to define a common base class for custom strategies:

[<AbstractClass>]
type CustomAuditInfoStrategy() =
  interface IAuditInfoStrategy with
    member this.Mode = AuditInfoMode.Custom
    member this.GetCurrentUser() = this.GetCurrentUserCore()
  abstract member GetCurrentUserCore : unit -> string

So far, this is just a normal F# abstract class, to save us having to reimplement Mode every time. Let’s put it to the all-important work of making someone else carry the can:

let blame x = {
  new CustomAuditInfoStrategy() with
    member this.GetCurrentUserCore() = x }

What does the blame function do? It creates a new CustomAuditInfoStrategy whose GetCurrentUserCore method always returns our designated scapegoat. Notice that we haven’t had to create a ScapegoatingStrategy class to contain this brutal but realistic business logic: we can do it inline in the blame function. This keeps the blaming logic within the blame function instead of spinning it out to a separate location. And of course we can call the function multiple times to shift the blame around as required. Let’s try it out:

let context = new LightSpeedContext()
context.AuditInfoMode <- AuditInfoMode.Custom
 
context.CustomAuditInfoStrategy <- blame "bob"
printfn "%A" (context.CustomAuditInfoStrategy.GetCurrentUser())
// prints "bob"
 
context.CustomAuditInfoStrategy <- blame "kate"
printfn "%A" (context.CustomAuditInfoStrategy.GetCurrentUser())
// prints "kate"

Of course, under the surface, the F# compiler is defining derived classes of CustomAuditInfoStrategy just as you would if you were implementing this in C#. (Load the compiled F# code into a C# decompiler such as Reflector if you want to see what’s really going on.) But at the source code level, you don’t need to worry about this: you can just spin up objects to implement interfaces or abstract classes, or just to tweak concrete classes, and save yourself the overhead of writing a special class definition out longhand.

Tagged as F#

Leave a Reply

Archives

Join our mailer

You should join our newsletter! Sent monthly:

Back to Top