DEV Community

Kasey Speakman
Kasey Speakman

Posted on • Updated on

Quick Tip -- F# Code Organization

When I started programming F# I would run into the conundrum of where to place types and the functions on those types. Today it occurred to me that I have settled into a nice pattern for this and I thought I would share it.

The directory structure looks something like this:

  • MyLibrary (project or even folder)
    • Types.fs
    • Widget.fs
    • Gismo.fs
    • ...

In functional programming types are shared things. They are the junction point between different functions -- both your own and code which uses your library. Here's the pattern for the Types file. Notice the [<AutoOpen>] attribute on the module.

// Types.fs
namespace MyLibrary

[<AutoOpen>]
module Types =

    // type defs go here, no functions

    type Widget =
        {
            ...
        }


    type Gismo =
        {
            ...
        }

    ...
Enter fullscreen mode Exit fullscreen mode

Here is a Widget module containing functions for the Widget type.

namespace MyLibrary

module Widget =

    // the Widget type is automatically available in namespace
    let create ... : Widget =
        {
            ...
        }


    // so is the Gismo type
    let toGismo widget : Gismo =
        ...


    let fromGismo gismo : Widget =
        ...

    ...
Enter fullscreen mode Exit fullscreen mode

Here's what happens when I use this library.

open MyLibrary

// Types.fs is automatically opened, exposing all types in it
// I can reference the Widget type without namespace
let returnsWidget ... : Widget =
    ...

// The Widget module is also exposed under the same name
let myFn ... =
    Widget.create ...
        |> ...
        |> Widget.toGismo
Enter fullscreen mode Exit fullscreen mode

The way we have defined the namespace, types, and modules, the Widget type and all the functions on it will be nicely packaged together under the name Widget simply by opening the library. If you were to try to create the Widget type and the Widget module in the same namespace or module, you would get a compiler error because they have the same name. Update: The previous sentence no longer seems to be true in newer versions of F#.

You could achieve something similar by adding static extension methods onto the Widget type, but the above is more idiomatic. With type extensions, you can also run into problems with circular references.

Top comments (0)