Saturday, January 3, 2009

Implementing interfaces in F#

Ever since the release of C# 3.0 I've been tending to write more and more functional-styled code, thanks to LINQ. So I thought, if I'm writing in a functional way, I might just as well use a real functional programming language! So I went to check out F#. I have been reading about F# for some time, mainly from Tomáš Petříček, Dustin Campbell and Don Syme, but never really tried it.

So, I wanted to start by rewriting a service in F#, a service that is 99% functional (in C#) and so I thought it was a good candidate for trying out F#. This service implements an interface defined somewhere else (another assembly) and is registered in the DI container so it can be injected into other components in the system.

I'm clearing this up because people could say that when coding in F# you do it in a functional style and thus you don't use interfaces. Ok, that may be so for something written from scratch, but most of us code against already working systems implemented in C# or VB.NET which are of course imperative OO languages so interfaces come up often.

For the purpose of this post, let's assume that I wanted to implement IComparer. I started by writing a little test in C#, just to be extra sure that it was interoperable:

[Test]
public void ComparerTest() { 
	var c = new Comp();
	c.Compare(1,2);
}

Then I went to F# side of things and wrote Comp:

#light
namespace FSharpPlayground
open System.Collections
type Comp() =
    interface IComparer with
        member x.Compare(a, b) = 0

but it wouldn't compile. WTF?! The F# assembly compiled just fine, but then the C# compiler complained that there wasn't a Compare method. I pulled Reflector, opened the F# assembly and saw this:

[Serializable, CompilationMapping(SourceConstructFlags.ObjectType)]
public class Comp : IComparer
{
    // Methods
    private int System-Collections-IComparer-Compare(object a, object b)
    {
        return 0;
    }
}

Forget the attributes, why is the Compare method called System-Collections-IComparer-Compare instead of just Compare? And why private? In my ignorance, I thought I was just making some stupid syntax mistake, which happens all the time when learning a new language, so I tried all of these...

type Comp() =
    interface IComparer with
        member x.Compare(a: obj, b: obj) = 0

type Comp() = class
    interface IComparer with
        member x.Compare(a: obj, b: obj) = 0
end

type Comp() = class
    interface IComparer with
        member public x.Compare(a: obj, b: obj) = 0
end

type Comp() = class
    interface IComparer with
        override x.Compare(a: obj, b: obj) = 0
end

type public Comp() = class
    interface IComparer with
        override x.Compare(a: obj, b: obj) = 0
end

...to no avail. They all compiled to the same IL.

Until I found this thread. The problem wasn't the syntax. Turns out, F# implements interfaces explicitly by default (just the opposite of what is normal practice in C#) and it currently has no way to implement interfaces implicitly. So, either you cast the object to the interface when you need the method, or you write a "forward method" that hides the casting from the caller:

type Comp() =
    interface IComparer with
        member x.Compare(a, b) = 0
    member x.Compare(a, b) = (x :> IComparer).Compare(a,b)

This may seem a little odd at first, but when you think about it, it all makes sense since it actually clears up any ambiguity. In my particular case, I just had to change the test to:

IComparer c = new Comp();
c.Compare(1,2); 

And since this was being injected by the container via the interface, I didn't need the "forward method" and no other code had to be changed.

Another interesting way to implement interfaces is to use the object expression syntax which looks similar to Java's anonymous classes:

let CompExp = {
    new IComparer with
        member x.Compare(a,b) = 0
}

But this is really a static instance, so the DI container can't mess with it to inject other dependencies, make proxies, manage its lifecycle, well, everything that containers do.

Another common pitfall while transitioning from C# to F# is that code order matters.

All in all, F# has been gentle to me in my first week with it. It has so many interesting features that it sometimes makes my head spin trying to absorb them. Exciting stuff! :-)

No comments: