Skip to content
AgileLogics
6 min read

Modules and Namespaces

Prerequisites: Functions

A module groups related values, functions, and types under a name — the main way F# code is organized.

module Temperature =
    let freezingC = 0.0

    let toFahrenheit c = c * 9.0 / 5.0 + 32.0
    let toCelsius f = (f - 32.0) * 5.0 / 9.0

module Format =
    let degrees unit value = $"%.1f{value}°{unit}"

printfn "%s" (Temperature.toFahrenheit 100.0 |> Format.degrees "F")
printfn "%s" (Temperature.toCelsius 98.6 |> Format.degrees "C")

open Temperature
printfn "%.1f" (toFahrenheit freezingC)
212.0°F
37.0°C
32.0
  • Access members with Module.name, or open Module to use them directly.
  • The standard library is organized the same way: List.map, String.concat, and Option.defaultValue are functions in the List, String, and Option modules.

Module per type

A common pattern is a type plus a module with the same name holding its functions:

type Money = { Amount: decimal; Currency: string }

module Money =
    let create currency amount = { Amount = amount; Currency = currency }

    let add a b =
        if a.Currency <> b.Currency then Error "currency mismatch"
        else Ok { a with Amount = a.Amount + b.Amount }

    let format m = $"{m.Amount:F2} {m.Currency}"

let usd = Money.create "USD"
match Money.add (usd 10m) (usd 2.5m) with
| Ok total -> printfn "%s" (Money.format total)
| Error e -> printfn "%s" e

printfn "%A" (Money.add (usd 1m) (Money.create "EUR" 1m))
12.50 USD
Error "currency mismatch"

Private members

Mark helpers private to hide them from code outside the module:

module Password =
    let private minLength = 8
    let private hasDigit (s: string) = s |> Seq.exists System.Char.IsDigit

    let isStrong (s: string) = s.Length >= minLength && hasDigit s

printfn "%b %b" (Password.isStrong "hunter2") (Password.isStrong "correct-horse-7")
false true

Files, modules, and namespaces in a project

In a project, each .fs file usually starts with a declaration:

// illustrative: Shipping.fs — a top-level module
module MyShop.Shipping

let cost weightKg = if weightKg < 1.0 then 4.99m else 9.99m
// illustrative: Domain.fs — a namespace holding types and modules
namespace MyShop.Domain

type Product = { Sku: string; Price: decimal }

module Product =
    let isFree p = p.Price = 0m
  • module A.B.C at the top makes the whole file one module.
  • namespace A.B holds types and modules, but not loose let values — namespaces are how .NET organizes libraries, and they're what C# code sees.
  • A script (.fsx) is implicitly a module named after the file.

Remember that file order in the .fsproj matters: a file can use modules only from files listed above it.

RequireQualifiedAccess and AutoOpen

[<RequireQualifiedAccess>]
module Color =
    let red = "#ff0000"

[<AutoOpen>]
module Helpers =
    let clamp lo hi x = max lo (min hi x)

printfn "%s %d" Color.red (clamp 0 10 42)
#ff0000 10

[<RequireQualifiedAccess>] forces callers to write Color.red (even after open), avoiding name clashes. [<AutoOpen>] opens a module automatically when its parent is opened — use it sparingly.