Reading — step 1 of 7
Learn
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…