Skip to content

F# Guide — Modules: Organizing Code with Namespaces

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about F# Guide. We cover key concepts, practical examples, and best practices to help you master this topic.

F# modules are containers for related functions, types, and values that help organize code, control scope, and create hierarchical namespaces for better project structure.

What You'll Learn

  • Creating and using modules
  • Module vs namespace differences
  • Nested modules
  • Module Accessibility
  • Opening modules

Why It Matters

Modules keep related code together and prevent naming conflicts. Well-organized modules make code discoverable and maintainable. Durga Antivirus Pro organizes its analysis engine across multiple modules.

Real-World Use

Large F# projects use modules for each subsystem: data access, business logic, API controllers, utilities, and configuration.

flowchart LR
    A["Modules"] --> B["Definition"]
    B --> C["Nesting"]
    C --> D["Access Control"]
    D --> E["Best Practices"]
    A:::current --> B
    style A fill:#2563eb,stroke:#2563eb,color:#fff
    style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b

Defining Modules

// Simple module
module Math

let add x y = x + y
let subtract x y = x - y
let multiply x y = x * y

// Usage: Math.add 5 3

Module with Types

module Shapes

type Shape =
    | Circle of float
    | Rectangle of float * float

let area shape =
    match shape with
    | Circle r -> System.Math.PI * r * r
    | Rectangle (w, h) -> w * h

let createCircle radius =
    if radius <= 0 then None
    else Some (Circle radius)

Namespaces vs Modules

// Namespace (container for modules and types)
namespace MyApp.Data

module Database =
    let connect connectionString = ...

module Queries =
    let getUsers = ...

Nested Modules

module App =
    module Config =
        let host = "localhost"
        let port = 8080

    module Services =
        let start () = printfn "Starting on %s:%d" Config.host Config.port

// Access: App.Config.host, App.Services.start()

Module Accessibility

module Internal =
    // Private to this module
    let private helper x = x * 2

    // Public
    let public process x = helper x + 1

// Internal.process is accessible
// Internal.helper is NOT accessible outside the module

Opening Modules

// Open a module to use its contents without qualification
open System
open Math  // Custom Math module from above

let result = add 5 3  // Instead of Math.add 5 3

// Open with alias
module MyList = List
let first = MyList.head [1; 2; 3]

Auto-Open

// Module auto-opens when containing namespace is opened
[<AutoOpen>]
module Utils =
    let greet name = sprintf "Hello, %s!" name

// greet is available without opening Utils

Common Mistakes

1. Confusing modules with namespaces

Namespaces contain modules and types. Modules contain values and functions. Understand the hierarchy.

2. Deeply nested modules

Don't nest modules more than 2-3 levels deep. Flat structures are easier to navigate.

3. Opening too many modules

Excessive opens pollute the namespace. Prefer qualified access or targeted opens.

4. Circular dependencies

Modules that reference each other cause compilation errors. Design acyclic dependency graphs.

5. Mixed accessibility

Be intentional about what's public vs private. Hide implementation details.

Practice Questions

1. What is the difference between a module and a namespace? Namespaces organize types and modules hierarchically. Modules organize values, functions, and types within a namespace.

2. How do you control visibility in a module? Use private keyword on bindings to hide them outside the module. Public is the default.

3. What does [] do? It makes the module's contents available without explicit open when the containing namespace is opened.

Challenge: Design a module structure for a web application with configuration, routing, controllers, and data access.

FAQ

{{< faq question="Can modules be split across files?" >}} Yes, using namespace declarations across files in the same namespace. But a module itself must be in one file. {{< /faq >}}

{{< faq question="Are modules compiled to classes?" >}} Yes, F# modules are compiled to static classes in IL. Module bindings become static methods and properties. {{< /faq >}}

{{< faq question="Can modules contain types?" >}} Yes, modules can define types that are scoped to the module. Types can also be in namespaces directly. {{< /faq >}}

{{< faq question="What is the module keyword do?" >} It creates a container with its own scope. Contents must be indented and are accessible via ModuleName.member. {{< /faq >}}

{{< faq question="Can I have module-level mutable state?" >}} Yes, but use sparingly. let mutable counter = 0 at module level creates shared mutable state. {{< /faq >}}

Mini Project

Organize a calculator application into modules:

module Calculator.Operations

let add x y = x + y
let subtract x y = x - y
let multiply x y = x * y
let divide x y =
    if y = 0 then Error "Division by zero"
    else Ok (x / y)

module Calculator.Parser

let parse (s: string) =
    let parts = s.Split(' ')
    match parts with
    | [|a; op; b|] -> Ok (int a, op, int b)
    | _ -> Error "Invalid format"

module Calculator.Engine

open Calculator.Operations

let calculate (a, op, b) =
    match op with
    | "+" -> Ok (add a b)
    | "-" -> Ok (subtract a b)
    | "*" -> Ok (multiply a b)
    | "/" -> divide a b
    | _ -> Error "Unknown operator"

What's Next

Now that you understand modules, explore advanced collection operations.

Topic Description Link
F# Collections Collection operations {{< ref "15-collections" >}}
F# Pipelines Data Pipelines {{< ref "16-pipelines" >}}

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro