Skip to content
Modules and Namespaces
step 1/7

Reading — step 1 of 7

Learn

~3 min readHigher-Order, Modules, Mutable

F# has TWO ways to organize code: modules (groups of bindings) and namespaces (groups of types/modules). Microsoft's F# docs cover both. Most F# code uses modules; namespaces appear in larger codebases or for .NET interop.

Modules

A module is a named group of let-bindings, types, and submodules:

module Math =
    let pi = 3.14159
    let area r = pi * r * r
    let circumference r = 2.0 * pi * r

// Use:
let a = Math.area 5.0

Functions and values inside the module are accessed via Module.name. Like Java packages but lighter weight.

File-level modules

At the top of a file, you can declare a module to wrap the whole file:

module MyApp.Core

// All bindings below belong to MyApp.Core
let hello () = printfn "hi"
let add a b = a + b

The filename and module name are independent; convention is to match.

Nested modules

module MyLib =
    module Strings =
        let upper (s: string) = s.ToUpperInvariant()
        let trim (s: string) = s.Trim()
    
    module Numbers =
        let double n = n * 2
        let square n = n * n

// Use:
let s = MyLib.Strings.upper "hello"
let n = MyLib.Numbers.double 5

open — bringing names into scope

open System
open System.IO
open MyLib.Strings

let content = File.ReadAllText("data.txt")
let shouted = upper "hello"               // no qualification needed

open is like using in C# or import in Python. Brings module/namespace contents into the current scope.

Namespaces

Namespaces group MODULES (not bare values). They're the classic .NET organization:

namespace MyApp.Models

module User =
    type t = { Name: string; Age: int }
    let create name age = { Name = name; Age = age }

module Order =
    type t = { Id: int; Total: float }
    let total o = o.Total

Namespaces can't have top-level bindings — only modules and types. Use namespaces for the .NET-style hierarchy; modules for grouping logic.

Module signatures (.fsi files)

For library APIs, separate signature files describe the public interface:

// MyLib.fsi — signature
module MyLib
val add : int -> int -> int
val helper : string -> string  // private to module body? define here too
// MyLib.fs — implementation
module MyLib
let add a b = a + b
let private secret_helper x = x  // not exposed
let helper s = ...

The .fsi file controls what's exported. Without one, all public bindings are exposed. Common in larger F# projects.

Visibility modifiers

module MyLib =
    let private hidden = 42                    // module-private
    let public visible = 1
    let internal sameAssembly = 2              // .NET internal

Without a modifier, top-level let bindings are public (in F# code) but compile to internal in .NET.

When use modules vs namespaces

  • Modules — for grouping related functions/values; the daily-use organization
  • Namespaces — for hierarchical organization of multiple modules; .NET interop; published libraries

Many F# projects use ONLY modules — flat or nested — without explicit namespaces. Namespaces shine in libraries shared with C#.

AutoOpen

[<AutoOpen>]
module Helpers =
    let helper x = x + 1

AutoOpen opens the module automatically when its parent is opened. Used for utility modules whose contents should always be in scope.

Common mistakes

  • Confusing modules and namespaces — namespaces hold modules and types only (no values). Modules hold anything.
  • Mismatched filename and module — works but confusing. Match by convention.
  • Forgetting open — the compiler complains about undefined names. Open the right modules at the top.
  • Public bindings without intent — F# defaults to public. Use a .fsi file or private modifier to hide internals.
  • Ordering — F# is order-sensitive — modules and bindings must be defined before use. The F# compiler reads top-to-bottom, file by file in project order.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…