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 =
{
...
}
...
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 =
...
...
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
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)