Thursday, April 2, 2009

Using Windsor in F#

I've been doing some experiments with F# which involves using Windsor (my default choice for a IoC container) and found that Windsor's fluent interface is... not so fluent when used in F#.

UPDATE 10/21/2010: The F# team has loosened the syntax a lot, F# 2.0 can now consume the Windsor fluent API without any changes.

Fluent interface as-is in F#

Example: given this code in C#

container.Register(Component.For<IMyServiceContract>().ImplementedBy<MyServiceImpl>());

Let's try to translate this to F#. (I'll do it step by step so it's more didactic)

Code: container.Register(Component.For<IMyServiceContract>().ImplementedBy<MyServiceImpl>())
Compiler says: Error: Successive arguments should be separated by spaces or tupled, and arguments involving function or method applications should be parenthesized.
Explanation: Component.For<IMyServiceContract>() is a method application, so it has to be parenthesized:

Code: container.Register((Component.For<IMyServiceContract>()).ImplementedBy<MyServiceImpl>())
Compiler says: Error: Type constraint mismatch. The type ComponentRegistration<IMyServiceContract> is not compatible with type IRegistration array.
The type 'ComponentRegistration<IMyServiceContract>' is not compatible with the type 'IRegistration array'.
Explanation: The reason for these errors is that F# expects an array as the parameter for Register, since it's signature is

IWindsorContainer Register(params IRegistration[] registrations)

and F# doesn't support params arrays, so we have to explicitly construct an array:

Code: container.Register([|(Component.For<IMyServiceContract>()).ImplementedBy<MyServiceImpl>()|])
Compiler says: Error: This expression has type ComponentRegistration<IMyServiceContract> but is here used with type IRegistration.
Explanation: What? But ComponentRegistration<IMyServiceContract> implements the IRegistration interface! Yeah, but Register() takes an array of IRegistration, not an array of ComponentRegistration<IMyServiceContract>, and F# doesn't implement array covariance. You can write this in C#:

IMyServiceContract[] arr = new MyServiceImpl[] { new MyServiceImpl() };

but you can't write this in F#:

let arr: IMyServiceContract[] = [| MyServiceImpl() |]

This is actually a Good Thing since this kind of covariance has some nasty consequences. So we have no option but to cast:

Code: container.Register [| (((Component.For<IMyServiceContract>()).ImplementedBy<MyServiceImpl>()) :> IRegistration) |]
Compiler says: Warning: This expression should have type 'unit', but has type 'IWindsorContainer'.
Explanation: We have to do something about the return value. We're not using it in this example, so we'll just discard it:

Code: let _ = container.Register [| (((Component.For<IMyServiceContract>()).ImplementedBy<MyServiceImpl>()) :> IRegistration) |]

This finally compiles, but it's way too noisy. Not fluent at all!

We could have also written:

let _ = container.Register(Seq.to_array (Seq.cast [(Component.For<IMyServiceContract>()).ImplementedBy<MyServiceImpl>()]))

but it's just as ugly.

Extension method solution

We could hide the casting and array stuff in an extension method:

module WindsorContainerExtensions = 
    type IWindsorContainer with
        member x.RegisterComponents (r: seq<#IRegistration>) = 
            let _ = x.Register (Seq.to_array (Seq.cast r))
            () 

which allows us to write:

container.RegisterComponents [(Component.For<IMyServiceContract>()).ImplementedBy<MyServiceImpl>()]

Much better, but it doesn't quite fit the F# spirit.

Function pipelines solution

A better-yet solution is to use function pipelining to build a DSL, like FsUnit and FsTest do, so we can write:

typeof<IMyServiceContract>
    |> implementedBy (typeof<MyServiceImpl>)
    |> registerIn container

Here are the functions that support this:

let implementedBy impl (service: Type) =
    (Component.For service).ImplementedBy impl       
    
let registerIn (container: IWindsorContainer) registration = 
    container.RegisterComponents [ registration ]

It's easy to follow this pattern and implement similar functions to cover more functionality. For example, this sets the lifestyle for a registration:

let withLifestyle lifestyle (registration: ComponentRegistration<_>) = 
    (registration.LifeStyle).Is lifestyle

Usage:

typeof<IMyServiceContract>
    |> implementedBy (typeof<MyServiceImpl>)
    |> withLifestyle LifestyleType.Transient
    |> registerIn container


Conclusion

Fluent interfaces are cool but quite language-dependent, so if you're designing an API targeting the CLR and thinking of building a fluent interface, make sure you also provide an alternative, simpler, non-fluent API so other languages can build their own flavor of fluent interface (Windsor does provide this, of course)

4 comments:

CKoenig said...

just a minor note:
instead of "let _ = ..." you can write "... |> ignore" that normaly looks somewhat nicer.

Anonymous said...

Also, you should be able to just use "upcast" rather than specify the exact type you want to cast to.

James Curran said...

Actually, I think the real problem is that any form of fleunt interface violates the spirit of functional languages,(that is "No side effects" when a fleunt interface is entirely side effects)

mausch said...

@James: yes, but not in this case, since I'm only consuming the API with F#. Mutability would be an issue if the API would have been built with F#. The problem here is that what's idiomatic for C# is likely not idiomatic for F#, therefore "fluency" is a highly language-dependent term and API designers should keep this in mind.