Why CompilationRepresentationAttribute is your friend

In this short post I show why CompilationRepresentationAttribute is your friend if you like the "union/struct plus module" way of organising code as seen in core language features like Option<'T> and the Option module.

Introduction

Dividing a piece of functionality between a struct or discriminated union representing the data and a module of the same name containing associated functions is a fairly common practice in F#. Good examples are the Option<'T> and Async<'T> types and associated Option and Async modules from F#'s core language features.

type Option<'T> = 
    | Some of 'T
    | None

module Option =
    ...

This style helps keep your code both tidy and readable, particularly when piping or composing multiple functions.

let runParallel = 
    Async.Parallel
    >> Async.Catch
    >> Async.RunSynchronously

CompilationRepresentationFlags

Writing F# in this style is straight forward if your union or record type is generic.

type Widget<'T> = {
    Value : 'T;
    Keywords : String list;
}   

module Widget = 
    let create value = ...
    let add keyword value = ...

The compiler will happily compile this code. When viewed in the object browser or from C# you can see that this code results in two types with different names - Widget<T> for the record type and Widget for the module, with the functions compiling to static methods.

However, you will run into problems if your union or record type is not generic.

type Widget = { ... }
module Widget = ...

This is because now the compiled representations of the record and module have the same name.

Duplicate definition of type, exception or module `Widget`

One way to avoid this is to simply change your naming convention - e.g. perhaps use Widget for the record/struct and Widgets for the module. However, if you prefer the names to match then F# lets you have your cake and eat it.

Enter CompilationRepresentationAttribute. This is an attribute that can be added to language elements to tell the F# compiler how they should be represented when compiled using the CompilationRepresentationFlags enumation.

Of relevance to this discussion is the ModuleSuffix value of the flags enumeration. When applied to a module this tells the F# compiler to add "Module" to the end of the type name.

type Widget = { ... }

[<CompilationRepresentation (CompilationRepresentationFlags.ModuleSuffix)>]
module Widget = ...

In the example above this would mean that the Widget module compiles to a type called WidgetModule, thus resolving our type name problems. The module is still accessible as Widget within F#, but in C# it would appear as WidgetModule. This allows us to continue to use the record/union plus module style despite the record and module having the same name.

let init =
    Widget.create
    >> Widget.add "Key"
    >> Widget.add "Words"

Comments