AutoOpen attribute in F#

A little late to the party, but I wanted to add another usage.

I tend to use [<AutoOpen>] to expose types within a namespace.

// SlimSql\Types.fs
namespace SlimSql

[<AutoOpen>]
module Types =

    type SqlOperation =
        {
            Statement : string
            Parameters : SqlParam list
        }

Then I can attach functions to the same type name without getting a compiler error that the name is already in use.

// SlimSql\SqlOperation.fs
namespace SlimSql

module SqlOperation =

    let merge (operations : SqlOperation list) : SqlOperation =
        ...

    let wrapInTransaction operation =
        ...

Then everything is nicely packaged up with the same name in consuming code. So when the user is looking for a behavior on SqlOperation data, they can naturally find it by typing SqlOperation. and Intellisense will show it. Much in the same way that types such as List are used in practice.

open SlimSql

let operations =
    [
        sql "INSERT INTO ...." [ p "@Value" 123; ... ]
        ...
    ]
let writeOp =
    operations
    |> SqlOperation.merge
    |> SqlOperation.wrapInTransaction

The SlimSql.Types module can also be opened by itself to access only the types for composition with other types.

I much prefer this solution to augmenting types with static members.


It can be used to organize a module into sub-modules but present a unified/single-module view externally:

module Outer =

  [<AutoOpen>]
  module Inner1 =
    let f1() = ()

  [<AutoOpen>]
  module Inner2 =
    let f2() = ()

open Outer

let x = f1()
let y = f2()

FParsec does this: open FParsec opens all sub-modules (Primitives, CharParsers, etc.).


I think the main use for the AutoOpen attribute is when you want to make some let-bound values available when the user of your library opens a namespace. This is where the attribute is very useful, because I think libraries should generally export all definitions in namespaces, but for some purposes you need to export values and values cannot be defined inside a namespace.

Here is an example from F# async extensions which defines a computation builder and thus it needs to export asyncSeq value (but at the same time, all functionality is wrapped in a namespace):

namespace FSharp.Async

type AsyncSeq<'T> = (* ... *)
type AsyncSeqBuilder() = (* ... *)

[<AutoOpen>]
module GlobalValues = 
  let asyncSeq = AsyncSeqBuilder()

The user of the library can just write open FSharp.Async and they will see asyncSeq. I think the same pattern is used with various math libraries (where you also want to export simple-named functions.)

For modules (e.g. List and Seq), I think most of the people do not use open and access the functions via a module name (such as List.map), so although you can use this for nested modules, I have not seen that as frequently.