10. Namespaces and Modules
F# is primarily an expression-based language. However, F# source code units are made up of declarations , some of which can contain further declarations. Declarations are grouped using namespace declaration groups , type definitions , and module definitions. These also have corresponding forms in signatures. For example, a file may contain multiple namespace declaration groups, each of which defines types and modules, and the types and modules may contain member, function, and value definitions, which contain expressions.
Declaration elements are processed in the context of an environment. The definition of the elements of an environment is found in §14.1.
namespace-decl-group :=
namespace long-ident module-elems -- elements within a namespace
namespace global module-elems -- elements within no namespace
module-defn :=
attributesopt module accessopt ident = module-defn-body
module-defn-body :=
begin module-elemsopt end
module-elem :=
module-function-or-value-defn -- function or value definitions
type-defns -- type definitions
exception-defn -- exception definitions
module-defn -- module definitions
module-abbrev -- module abbreviations
import-decl -- import declarations
compiler-directive-decl -- compiler directives
module-function-or-value-defn :=
attributesopt let function-defn
attributesopt let value-defn
attributesopt let rec opt function-or-value-defns
attributesopt do expr
import-decl := open long-ident
module-abbrev := module ident = long-ident
compiler-directive-decl := # ident string ... string
module-elems := module-elem ... module-elem
access :=
private
internal
public
10.1 Namespace Declaration Groups
Modules and types in an F# program are organized into namespaces, which encompass the identifiers that are defined in the modules and types. New components may contribute entities to existing namespaces. Each such contribution to a namespace is called a namespace declaration group.
In the following example, the MyCompany.MyLibrary
namespace contains Values
and x
:
namespace MyCompany.MyLibrary
module Values1 =
let x = 1
A namespace declaration group is the basic declaration unit within an F# implementation file and is of the form
namespace long-ident
module-elems
The long-ident
must be fully qualified. Each such group contains a series of module and type
definitions that contribute to the indicated namespace. An implementation file may contain multiple
namespace declaration groups, as in this example:
namespace MyCompany.MyOtherLibrary
type MyType() =
let x = 1
member v.P = x + 2
module MyInnerModule =
let myValue = 1
namespace MyCompany.MyOtherLibrary.Collections
type MyCollection(x : int) =
member v.P = x
Namespace declaration groups may not be nested.
A namespace declaration group can contain type and module definitions, but not function or value definitions. For example:
namespace MyCompany.MyLibrary
// A type definition in a namespace
type MyType() =
let x = 1
member v.P = x+2
// A module definition in a namespace
module MyInnerModule =
let myValue = 1
// The following is not allowed: value definitions are not allowed in namespaces
let addOne x = x + 1
When a namespace declaration group N
is checked in an environment env
, the individual
declarations are checked in order and an overall namespace declaration group signature Nsig
is
inferred for the module. An entry for N
is then added to the ModulesAndNamespaces table in the
environment env
(see §14.1.3).
Like module declarations, namespace declaration groups are processed sequentially rather than simultaneously, so that later namespace declaration groups are not in scope when earlier ones are processed. This prevents invalid recursive definitions.
In the following example, the declaration of x
in Module1
generates an error because the
Utilities.Part2
namespace is not in scope:
namespace Utilities.Part1
module Module1 =
let x = Utilities.Part2.Module2.x + 1 // error (Part2 not yet declared)
namespace Utilities.Part2
module Module2 =
let x = Utilities.Part1.Module1.x + 2
Within a namespace declaration group, the namespace itself is implicitly opened if any preceding namespace declaration groups or referenced assemblies contribute to it. For example:
namespace MyCompany.MyLibrary
module Values1 =
let x = 1
namespace MyCompany.MyLibrary
// Here, the implicit open of MyCompany.MyLibrary brings Values1 into scope
module Values2 =
let x = Values1.x
10.2 Module Definitions
A module definition is a named collection of declarations such as values, types, and function values. Grouping code in modules helps keep related code together and helps avoid name conflicts in your program. For example:
module MyModule =
let x = 1
type Foo = A | B
module MyNestedModule =
let f y = y + 1
type Bar = C | D
When a module definition M
is checked in an environment env0
, the individual declarations are
checked in order and an overall module signature Msig
is inferred for the module. An entry for M
is
then added to the ModulesAndNamespaces table to environment env0
to form the new environment
used for checking subsequent modules.
Like namespace declaration groups, module definitions are processed sequentially rather than simultaneously, so that later modules are not in scope when earlier ones are processed.
module Part1 =
let x = Part2.StorageCache() // error (Part2 not yet declared)
module Part2 =
type StorageCache() =
member cache.Clear() = ()
No two types or modules may have identical names in the same namespace. The
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
attribute adds the
suffix Module
to the name of a module to distinguish the module name from a type of a similar name.
For example, this is frequently used when defining a type and a set of functions and values to manipulate values of this type.
type Cat(kind: string) =
member x.Meow() = printfn "meow"
member x.Purr() = printfn "purr"
member x.Kind = kind
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module Cat =
let tabby = Cat "Tabby"
let purr (c:Cat) = c.Purr()
let purrTwice (c:Cat) = purr(); purr()
Cat.tabby |> Cat.purr |> Cat.purrTwice
10.2.1 Function and Value Definitions in Modules
Function and value definitionsin modules introduce named values and functions.
let rec~opt function-or-value-defn1 and ... and function-or-value-defnn
The following example defines value x
and functions id
and fib
:
module M =
let x = 1
let id x = x
let rec fib x = if x <= 2 then 1 else fib (n - 1) + fib (n - 2)
Function and value definitions in modules may declare explicit type variables and type constraints:
let pair<'T>(x : 'T) = (x, x)
let dispose<'T when 'T :> System.IDisposable>(x : 'T) = x.Dispose()
let convert<'T, 'U>(x) = unbox<'U>(box<'T>(x))
A value definition that has explicit type variables is called a type function (§10.2.3).
Function and value definitions may specify attributes:
// A value definition with the System.Obsolete attribute
[<System.Obsolete("Don't use this")>]
let oneTwoPair = ( 1 , 2 )
// A function definition with an attribute
[<System.Obsolete("Don't use this either")>]
let pear v = (v, v)
By the use of pattern matching, a value definition can define more than one value. In such cases, the attributes apply to each value.
// A value definition that defines two values, each with an attribute
[<System.Obsolete("Don't use this")>]
let (a, b) = (1, 2)
Values may be declared mutable:
// A value definition that defines a mutable value
let mutable count = 1
let freshName() = (count <- count + 1; count)
Function and value definitions in modules are processed in the same way as function and value definitions in expressions (§14.6), with the following adjustments:
- Each defined value may have an accessibility annotation (§10.5). By default, the accessibility
annotation of a function or value definition in a module is
public
. - Each defined value is externally accessible if its accessibility annotation is
public
and it is not hidden by an explicit signature. Externally accessible values are guaranteed to have compiled CLI representations in compiled CLI binaries. - Each defined value can be used to satisfy the requirements of any signature for the module (§11.2).
- Each defined value is subject to arity analysis (§14.11).
- Values may have attributes, including the
ThreadStatic
orContextStatic
attribute.
10.2.2 Literal Definitions in Modules
Value definitions in modules may have the Literal
attribute. This attribute causes the value to be
compiled as a constant. For example:
[<Literal>]
let PI = 3.141592654
Literal values may be used in custom attributes and pattern matching. For example:
[<Literal>]
let StartOfWeek = System.DayOfWeek.Monday
[<MyAttribute(StartOfWeek)>]
let feeling(day) =
match day with
| StartOfWeek -> "rough"
| _ -> "great"
A value that has the Literal
attribute is subject to the following restrictions:
- It may not be marked
mutable
orinline
. - It may not also have the
ThreadStatic
orContextStatic
attributes. - The right-hand side expression must be a literal constant expression that is both a valid expression after checking, and is made up of either:
- A simple constant expression, with the exception of
()
, native integer literals, unsigned native integer literals, byte array literals, BigInteger literals, and user-defined numeric literals.
— OR —
- A reference to another literal
— OR —
- A bitwise combination of literal constant expressions
— OR —
- A
+
concatenation of two literal constant expressions which are strings
— OR —
enum x
orLanguagePrimitives.EnumOfValue x
wherex
is a literal constant expression.
10.2.3 Type Function Definitions in Modules
Value definitions within modules may have explicit generic parameters. For example, ‘T
is a generic
parameter to the value empty
:
let empty<'T> : (list<'T> * Set<'T>) = ([], Set.empty)
A value that has explicit generic parameters but has arity []
(that is, no explicit function parameters)
is called a type function. The following are some example type functions from the F# library:
val typeof<'T> : System.Type
val sizeof<'T> : int
module Set =
val empty<'T> : Set<'T>
module Map =
val empty<'Key,'Value> : Map<'Key,'Value>
Type functions are rarely used in F# programming, although they are convenient in certain situations. Type functions are typically used for:
- Pure functions that compute type-specific information based on the supplied type arguments.
- Pure functions whose result is independent of inferred type arguments, such as empty sets and maps.
Type functions receive special treatment during generalization (§14.6.7) and signature conformance
(§11.2). They typically have either the RequiresExplicitTypeArguments
attribute or the
GeneralizableValue
attribute. Type functions may not be defined inside types, expressions, or
computation expressions.
In general, type functions should be used only for computations that do not have observable side
effects. However, type functions may still perform computations. In this example, r
is a type function
that calculates the number of times it has been called
let mutable count = 1
let r<'T> = (count <- count + 1); ref ([] : 'T list);;
// count = 1
let x1 = r<int>
// count = 2
let x2 = r<int>
// count = 3
let z0 = x1
// count = 3
The elaborated form of a type function is that of a function definition that takes one argument of
type unit
. That is, the elaborated form of
let ident typar-defns = expr
is the same as the compiled form for the following declaration:
let ident typar-defns () = expr
References to type functions are elaborated to invocations of such a function.
10.2.4 Active Pattern Definitions in Modules
A value definition within a module that has an active-pattern-op-name
introduces pattern-matching
tags into the environment when the module is accessed or opened. For example,
let (|A|B|C|) x = if x < 0 then A elif x = 0 then B else C
introduces pattern tags A
, B
, and C
into the PatItems table in the name resolution environment.
10.2.5 “do” statements in Modules
A do
statement within a module has the following form:
do expr
The expression expr
is checked with an arbitrary initial type ty
. After checking expr
, ty
is asserted to
be equal to unit
. If the assertion fails, a warning rather than an error is reported. This warning is
suppressed for plain expressions without do in script files (that is, .fsx and .fsscript files).
A do
statement may have attributes. In this example, the STAThread
attribute specifies that main
uses the single-threaded apartment (STA) threading model of COM:
let main() =
let form = new System.Windows.Forms.Form()
System.Windows.Forms.Application.Run(form)
[<STAThread>]
do main()
10.3 Import Declarations
Namespace declaration groups and module definitions can include import declarations in the following form:
open long-ident
Import declarations make elements of other namespace declaration groups and modules accessible by the use of unqualified names. For example:
open FSharp.Collections
open System
Import declarations can be used in:
- Module definitions and their signatures.
- Namespace declaration groups and their signatures.
An import declaration is processed by first resolving the long-ident
to one or more namespace
declaration groups and/or modules [ F1
, ..., Fn
] by Name Resolution in Module and Namespace Paths
(§14.1.2). For example, System.Collections.Generic
may resolve to one or more namespace
declaration groups—one for each assembly that contributes a namespace declaration group in the
current environment. Next, each Fi
is added to the environment successively by using the technique
specified in §14.1.3. An error occurs if any Fi
is a module that has the RequireQualifiedAccess
attribute.
10.4 Module Abbreviations
A module abbreviation defines a local name for a module long identifier, as follows:
module ident = long-ident
For example:
module Ops = FSharp.Core.Operators
Module abbreviations can be used in:
- Module definitions and their signatures.
- Namespace declaration groups and their signatures.
Module abbreviations are implicitly private to the module or namespace declaration group in which they appear.
A module abbreviation is processed by first resolving the long-ident
to a list of modules by Name
Resolution in Module and Namespace Paths (see §14.1). The list is then appended to the set of
names that are associated with ident
in the ModulesAndNamespaces table.
Module abbreviations may not be used to abbreviate namespaces.
10.5 Accessibility Annotations
Accessibilities may be specified on declaration elements in namespace declaration groups and modules, and on members in types. The table lists the accessibilities that can appear in user code:
Accessibility | Description |
---|---|
public |
No restrictions on access. |
private |
Access is permitted only from the enclosing type, module, or namespace declaration group. |
internal |
Access is permitted only from within the enclosing assembly, or from assemblies whose name is listed using the InternalsVisibleTo attribute in the current assembly. |
The default accessibilities are public
. Specifically:
- Function definitions, value definitions, type definitions, and exception definitions in modules are public.
- Modules, type definitions, and exception definitions in namespaces are public.
- Members in type definitions are public.
Some function and value definitions may not be given an accessibility and, by their nature, have restricted lexical scope. In particular:
- Function and value definitions in classes are lexically available only within the class being defined, and only from the point of their definition onward.
- Module type abbreviations are lexically available only within the module or namespace declaration group being defined, and only from their point of their definition onward.
Note that:
private
on a member means “private to the enclosing type or module.”private
on a function or value definition in a module means “private to the module or namespace declaration group.”private
on a type, module, or type representation in a module means “private to the module.”
The CLI compiled form of all non-public entities is internal
.
Note: The
family
andprotected
specifications are not supported in this version of the F# language.
Accessibility modifiers can appear only in the locations summarized in the following table.
Component | Location | Example |
---|---|---|
Function or value definition in module |
Precedes identifier | let private x = 1 let inline private f x = 1 let mutable private x = 1 |
Module definition | Precedes identifier | module private M = let x = 1 |
Type definition | Precedes identifier | type private C = A \| B type private C<'T> = A \| B |
val definition in a class |
Precedes identifier | val private x : int |
Explicit constructor | Precedes identifier | private new () = { inherit Base } |
Implicit constructor | Precedes identifier | type C private() = ... |
Member definition | Precedes identifier, but cannot appear on inherit definitions, interface definitions, abstract definitions, individual union cases. Accessibility for inherit , interface , and abstract definitions is always the same as that of the enclosing class. |
member private x.X = 1 |
Explicit property get or set in a class |
Precedes identifier | member __.Item with private get i = 1 and private set i v = () |
Type representation | Precedes identifier | type Cases = private \| A \| B |