REVISED: Thursday, September 11, 2025
Let's break it down using the Adder
module from the previous tutorial as an example.
A. The Basic Structure: The Implementation (.ml
file)
The fundamental syntax for creating a module is module ModuleName = struct ... end
.
module ModuleName
: This declares a new module. A key rule in OCaml is that module names must start with a capital letter. In the example, this ismodule Adder
.struct ... end
: This block contains the implementation of the module. Everything defined betweenstruct
andend
is part of that module.
In the previous tutorial, the implementation is:
(** Adds two integers and returns the result. *)
let add (a : int) (b : int) : int = a + b
end
Defined here is a module named Adder
that contains a single function named add
.
B. Accessing Module Contents
To use a function or value from a module, you use the "dot notation": ModuleName.function_name
.
This is exactly what you see later in the main_loop
function:
This tells the OCaml compiler to look inside the Adder
module to find the add
function. This prevents naming conflicts. For instance, you could have another module, Subtractor
, with its own calculate
function, and you would access it via Subtractor.calculate
.
C. The Public API: The Interface (.mli
file)
While the current script only uses one file (.ml
), a crucial part of module programming in OCaml is the concept of an interface or signature. The interface defines which parts of the module are publicly accessible.
Interfaces are placed in a file with the same name as the implementation, but with a
.mli
extension.For the
adder.ml
file, you could create anadder.mli
file.The interface file specifies the types of the values you want to expose, but not their implementation.
An interface for the Adder
module would look like this in a file named adder.mli
:
(** Exposes a function that adds two integers.
@param a The first integer.
@param b The second integer.
@return The sum of a and b. *)
val add : int -> int -> int
What this does:
val add : int -> int -> int
: This line declares that there is a value namedadd
available to the public, and its type is a function that takes anint
, then anotherint
, and returns anint
.Encapsulation: If there were other "helper" functions inside the
Adder
module inadder.ml
that you did not list inadder.mli
, they would be private. Nobody outside theadder.ml
file could see or use them. This is the core of encapsulation and information hiding in OCaml.
When you compile your code, the OCaml compiler automatically pairs adder.ml
with adder.mli
and ensures that the implementation correctly matches the public interface you've defined.
.ml
file using module ModuleName = struct ... end
. Module names must be capitalized..mli
file to declare which parts of the module are public using val value_name : type
.ModuleName.function_name
) to use the public parts of the module from other files or modules.Bird, R. (2015). Thinking Functionally with Haskell. Cambridge, England: Cambridge University Press.
Davie, A. (1992). Introduction to Functional Programming Systems Using Haskell. Cambridge, England: Cambridge University Press.
Lipovača, M. (2011). Learn You a Haskell for Great Good!: A Beginner's Guide. San Francisco, CA: No Starch Press, Inc.
Thompson, S. (2011). The Craft of Functional Programming. Edinburgh Gate, Harlow, England: Pearson Education Limited.