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:

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

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

    ReplyDelete
  3. 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)

    ReplyDelete
  4. @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.

    ReplyDelete