Module
Syntax
accessibility-modifier
: restrict accessibility
ā public
(default), internal
(assembly), private
(parent)
Full name ([namespace.]module-name
) must be unique
ā 2 files cannot declare modules with the same name
Top-level module
Only one top-level module per file ā Declared on very top of the file
Can (should?) be qualified ā Attached to a parent namespace (already declared or not)
Contains all the rest of the file ā Unindented content š
Implicit top-level module
For a file without top-level module/namespace
Module name = file name
Without extension
With 1st letter in uppercase
E.g.:
program.fs
āmodule Program
āļø Not recommended in .fsproj
Local module
Syntax similar to let
The
=
sign after the local module name āIndent the entire content
Content
A module, local as top-level, can contain:
local types and sub-modules
values, functions
Key difference: ā content indentation
top-level
No
local
Yes
Module/static class equivalence
This F# module is equivalent to the following static class in CāÆ:
Module nesting
As with C⯠classes, F⯠modules can be nested.
ā Notes :
Interesting with private nested module to isolate/group
Otherwise, prefer a flat view
F⯠classes cannot be nested
Top-level vs local module
Qualifiable
ā
ā
=
sign + indented content
ā
ā ā
Top-level module ā 1st element declared in a file
Otherwise (after a top-level module/namespace) ā local module
Recursive module
Same principle as recursive namespace ā Convenient for a type and a related module to see each other
ā Recommendation: limit the size of recursive zones as much as possible
Module annotation
2 opposite attributes impact the module usage
[<RequireQualifiedAccess>]
[<RequireQualifiedAccess>]
Prevents the module import hence any unqualified use of its elements
ā š” Useful for avoiding shadowing for common names: add
, parse
...
[<AutoOpen>]
[<AutoOpen>]
Import module at same time as the parent namespace/module ā š” Handy for "mounting" values/functions at namespace level ā ā ļø Pollutes the current scope
ā ļø Scope
When the parent module/namespace has not been imported, AutoOpen
has no effect: to access the contents of the child module, the qualification includes not only the parent module/namespace, but also the child module:
ā Parent.childFunction
ā
ā Parent.Child.childFunction
ā
š” AutoOpen
is commonly used to better organize a module, by grouping elements into child modules
ā provided they remain of a reasonable size, otherwise it would be better to consider extracting them to different files.
This can be combined with making some modules private
to hide all their contents from the calling code, while keeping these contents directly accessible to the rest of the current module.
š Having an AutoOpen
module inside a RequireQualifiedAccess
module only makes sense if the module is private
.
AutoOpen
, RequireQualifiedAccess
or nothing?
AutoOpen
, RequireQualifiedAccess
or nothing?Let's consider a Cart
type with its Cart
companion module.
How do we call the function that adds an item to the cart? ā It depends on the function name.
addItem item cart
: ā[<RequireQualifiedAccess>]
to consider ā to be compelled to useCart.addItem
addItemToCart item cart
: ā function name is self-explicit ā[<AutoOpen>]
interesting to preventCart.addItemToCart
ā Works only ifCart
parent (if any) is notRequireQualifiedAccess
and opened
If the Cart
module contains other functions like this, it's probably better to apply the same naming convention to all of them.
Types-Modules main typologies
(Not exhaustive)
Type + Companion module containing function dedicated to this type
Multi-type module: several small types + related functions
Mapper modules: to map between 2 types sets
Type + Companion module
FSharp.Core style - see List
, Option
, Result
...
Module can have the same name as the type
ā BCL interop: module compiled name = {Module}Module
Multi-type module
Contains several small types + related functions (eventually)
Mapper modules
To map between 2 types sets
Module vs namespace
If a file contains a single module
Prefer top-level module in general
Prefer namespace + local module for BCL interop
Open type (Since F⯠5)
Use cases:
1. Import static classes to get direct access to methods
vs
āļø In general, use case only recommended for classes designed for this usage.
2. Cherry-pick imports
ā Import only the types needed in a module
Last updated
Was this helpful?