Namespaces and Modules
Clef is primarily an expression-based language. 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. A file may contain multiple namespace declaration groups; 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 § Name Resolution.
NORMATIVE: Modules and namespaces in Clef organize names, not compilation. Mutual references between modules — and between types or values within modules — are resolved by CCS’s syntactic dependency analysis (see § Compilation Pipeline), not by recursion keywords. Clef has no
let rec, noandfor grouping mutual definitions, nomodule rec, and nonamespace rec. The[<AutoOpen>]attribute is not part of Clef; module re-exports are explicit. See § for the attribute set.
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 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
publicNamespace 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 = 1A namespace declaration group is the basic declaration unit within an F# implementation file and is of the form
namespace long-ident
module-elemsThe 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 = xNamespace 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 + 1When a namespace declaration group N is checked in an environment env, the individual
declarations are checked and an overall namespace declaration group signature Nsig is inferred.
An entry for N is added to the ModulesAndNamespaces table in the environment env
(see § Opening Modules and Namespace Declaration Groups).
NORMATIVE: Namespace declaration groups SHALL be processed in dependency order, computed by CCS from the syntactic export map (see § Compilation Pipeline). Mutual references between namespace declaration groups are supported without ceremony.
The following code is well-formed in Clef regardless of the textual order of the two namespaces:
namespace Utilities.Part1
module Module1 =
let x = Utilities.Part2.Module2.x + 1
namespace Utilities.Part2
module Module2 =
let x = Utilities.Part1.Module1.x - 1CCS detects the mutual reference, elaborates the two namespaces as a single dependency group, and exposes the strongly-connected components of the file-level reference graph through its public API. A team policy or build configuration MAY require all cycles to be reviewed; the policy enforcement is a tool concern, not a language concern.
All contributions to a namespace are in mutual scope. A module Values2 = in namespace MyCompany.MyLibrary may reference Values1 declared in another namespace MyCompany.MyLibrary
block — in the same file or a different file, in any source order:
namespace MyCompany.MyLibrary
module Values1 =
let x = 1
namespace MyCompany.MyLibrary
module Values2 =
let x = Values1.xModule 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 | DWhen 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.
Module definitions, like namespace declaration groups, are processed in dependency order. Mutual references between modules are supported without recursion keywords.
The following code is well-formed in Clef regardless of textual module order:
module Part1 =
let x = Part2.StorageCache()
module Part2 =
type StorageCache() =
member cache.Clear() = ()No two types or modules may have identical names in the same namespace. If a type and a module in the same assembly and namespace are given the same name, the compiler will automatically append Module as a suffix to the compiled name. The
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>] attribute can be used to cause a module name to always be appended with Module.
type Cat(kind: string) =
member x.Meow() = printfn "meow"
member x.Purr() = printfn "purr"
member x.Kind = kind
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module Cat = // Cat is explicitly compiled as CatModule
let tabby = Cat "Tabby"
let purr (c:Cat) = c.Purr(); c
let purrTwice (c:Cat) = purr (purr c)
Cat.tabby |> Cat.purr |> Cat.purrTwice
type Dog(kind: string) =
member x.Bark() = printfn "woof"
member x.Wag() = printfn "wag"
member x.Kind = kind
module Dog = // Dog is implicitly compiled as DogModule
let beagle = Dog "Beagle"
let bark (d:Dog) = d.Bark(); d
let barkTwice (d:Dog) = bark (bark d)
Dog.beagle |> Dog.bark |> Dog.barkTwice
type Cage<'Pet>(pet: 'Pet) =
member x.Pet = pet
module Cage = // Cage is compiled as Cage because Cage<'Pet> is a generic type so the name does not conflict
let putInCage pet = Cage pet
let getPet (cage: Cage<'Pet>) = cage.Pet
let catCage = Cage.putInCage Cat.tabby
let dogCage = Cage.putInCage Dog.beagle
catCage |> Cage.getPet |> Cat.purr
dogCage |> Cage.getPet |> Dog.barkFunction and Value Definitions in Modules
Function and value definitionsin modules introduce named values and functions.
let function-or-value-defnThe following example defines value x and functions id and fib. Recursion is implicit; CCS
detects the self-reference in fib from its body:
module M =
let x = 1
let id x = x
let fib x = if x <= 2 then 1 else fib (x - 1) + fib (x - 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 :> 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 (§ Type Function Definitions in Modules).
Function and value definitions may specify attributes:
// A value definition with the Obsolete attribute
[<Obsolete("Don't use this")>]
let oneTwoPair = ( 1 , 2 )
// A function definition with an attribute
[<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
[<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 (§ Checking and Elaborating Function Value and Member Definitions), with the following adjustments:
- Each defined value may have an accessibility annotation (§ Accessibility Annotations). 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
publicand it is not hidden by an explicit signature. Externally accessible values are guaranteed to have compiled representations in the output binary. - Each defined value can be used to satisfy the requirements of any signature for the module (§ Signature Conformance).
- Each defined value is subject to arity analysis (§ Arity Inference).
- Values may have attributes.
Clef Note: The
ThreadStaticandContextStaticattributes are not available in Clef. Thread-local storage uses platform-specific mechanisms.
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.141592654Literal values may be used in custom attributes and pattern matching. For example:
[<Literal>]
let StartOfWeek = 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
mutableorinline.It may not also have the
ThreadStaticorContextStaticattributes.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 stringsor
enum xorLanguagePrimitives.EnumOfValue xwherexis a literal constant expression.
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 sizeof<'T> : int
module Set =
val empty<'T> : Set<'T>
module Map =
val empty<'Key,'Value> : Map<'Key,'Value>Clef Note: The
typeof<'T>type function is not available in Clef because there is no runtime type system. Type information is resolved entirely at compile time. Usesizeof<'T>for size queries.
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 (§ Generalization) and signature conformance
(§ Signature Conformance). 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 = expris the same as the compiled form for the following declaration:
let ident typar-defns () = exprReferences to type functions are elaborated to invocations of such a function.
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 Cintroduces pattern tags A, B, and C into the PatItems table in the name resolution environment.
“do” statements in Modules
A do statement within a module has the following form:
do exprThe 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 interactive (.clefx) files.
A do statement may have attributes:
let main() =
Console.WriteLine "Application started"
0
[<EntryPoint>]
do main() |> ignoreImport Declarations
Namespace declaration groups and module definitions can include import declarations in the following form:
open long-identImport declarations make elements of other namespace declaration groups and modules accessible by the use of unqualified names. For example:
open FSharp.Collections
open SystemImport 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
(§ Name Resolution in Module and Namespace Paths). For example, Collections may resolve to one or more namespace
declaration groups, one for each source package 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 § Opening Modules and Namespace Declaration Groups. An error occurs if any Fi is a module that has the RequireQualifiedAccess
attribute.
Module Abbreviations
A module abbreviation defines a local name for a module long identifier, as follows:
module ident = long-identFor example:
module Ops = FSharp.Core.OperatorsModule 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 § Name Resolution). 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.
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:
privateon a member means “private to the enclosing type or module.”privateon a function or value definition in a module means “private to the module or namespace declaration group.”privateon a type, module, or type representation in a module means “private to the module.”
Non-public entities are not accessible from outside their defining scope.
Clef Note: Accessibility is enforced at compile time. Non-public symbols are not exported in the native binary’s symbol table.
Note: The
familyandprotectedspecifications 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 = 1let inline private f x = 1let mutable private x = 1 |
| Module definition | Precedes identifier | module private M =let x = 1 |
| Type definition | Precedes identifier | type private C = A | Btype 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 oninherit definitions,interface definitions,abstract definitions,individual union cases. Accessibility for inherit, interface,and abstract definitions isalways the same as that of the enclosing class. | member private x.X = 1 |
| Explicit property get or set in a class | Precedes identifier | member __.Itemwith private get i = 1and private set i v = () |
| Type representation | Precedes identifier | type Cases = private | A | B |