Anatomy of an OCaml file
OCaml is a general-purpose, industrial-strength functional programming language with a strong static type system and type inference.
File extensions: .ml, .mli
Every part of the example below is labelled and explained. This page is one of 55 annotated tours on AnatomyOf, a free, open-source project by LunarWerx Studios.
What is inside an OCaml file
Comment
Block (* ... *) or doc comment (** ... *), ignored by the compiler.
OCaml has only one comment syntax, (* ... *), and it nests: a (* commented-out (* nested *) block *) closes correctly, unlike C-style /* */. There is no dedicated single-line comment token; a (* ... *) that happens to end before the newline reads the same as a line comment in practice. A comment opening with a second star, (** ... *), is a documentation comment consumed by ocamldoc or odoc to generate API references. Comments are stripped entirely before compilation and carry zero runtime cost.
Open statement
Makes the contents of another module available without a prefix.
open Printf brings every value the Printf module exports into unqualified scope, so printf can be written instead of Printf.printf. Without the open, every reference to a module's contents needs the qualified form. Because open can shadow existing names silently, style guides often prefer a local let open Printf in ... or the Module.(expr) syntax to scope the effect narrowly, reserving a top-level open for modules like Printf or Stdlib submodules that are used pervasively.
Type definition
Defines a new data type (record, variant, alias) with type.
type point = { x: float; y: float } declares a record type: a fixed set of named, typed fields. OCaml also uses type for variants (type shape = Circle of float | Rect of float * float), tuples, and simple aliases, one keyword covers most of the type-level vocabulary. OCaml's type inference means annotations like : float are frequently optional elsewhere in the program, but a type definition itself must spell out its shape once so the compiler can check every later usage against it.
Field (record member)
A named, typed component of a record type.
Each field in a record declaration, like x: float, pairs a name with a type; fields are separated by semicolons inside the braces. Field names must be unique across a compilation unit unless disambiguated, since OCaml resolves a bare field name like x using the most recently defined record that declares it. Fields are accessed with dot syntax (p.x) and are immutable by default. Writing to one requires declaring it mutable in the type definition and using the <- operator to update it.
Function definition (let)
let defines a function or value; rec allows it to call itself.
let binds a name to a value, and a function is just a value whose type happens to be an arrow (int -> int). Ordinary let bindings cannot refer to themselves. let rec is required whenever the definition needs to recurse, as in let rec factorial n = .... Because functions are ordinary values, they can be passed as arguments, returned from other functions, and partially applied by supplying fewer arguments than the function expects, which yields a new function waiting for the rest.
Pattern matching
Deconstructs a value against a series of patterns with match ... with.
match n with | 0 -> 1 | _ -> ... compares n against each |-prefixed pattern in order and evaluates the branch of the first match; the underscore _ is a wildcard that matches anything, conventionally used as the final catch-all. Patterns can destructure tuples, records, and variant constructors in one step, binding sub-values directly. The compiler performs exhaustiveness checking: if a match fails to cover every possible constructor of a variant type, it emits a warning at compile time, turning a whole category of runtime crashes into a build-time signal instead.
Local binding (let ... in)
Creates a name scoped to the expression that follows in.
let p = { x = 3.0; y = 4.0 } in ... binds p only for the expression after in: once that expression finishes evaluating, the binding is gone. This is different from a top-level let, which extends to the rest of the file or module. Because everything in OCaml is an expression, let ... in chains naturally: each binding's scope is exactly the rest of the enclosing expression, which is why deeply nested logic often reads as a sequence of small named steps rather than a block of statements.
Record instantiation
Creates a new value of a record type by supplying every field.
{ x = 3.0; y = 4.0 } builds a new point value; the compiler infers which record type is meant from the field names in scope (or from an expected type at the call site). Every field must be given a value. There is no notion of a partially-initialized record. Records are immutable values unless individual fields were declared mutable, and the { old_record with field = new_value } syntax produces a fresh copy with just one field changed, leaving the original untouched.
Function call
Applies a function to its arguments, written with juxtaposition.
Function application needs no parentheses or commas between arguments: printf "%.1f" p.x simply lists the function then each argument separated by whitespace, and factorial 5 applies factorial to 5. Parentheses are only needed to group a sub-expression, such as (factorial 5) when it appears as an argument to another call. printf is type-checked against its format string at compile time: a %d demands an int and a %.1f demands a float, so passing the wrong type is a compile error, not a runtime format-string bug.
Anonymous function (fun)
A function without a name, written fun x -> ....
fun x -> x * x is an unnamed function value, commonly passed directly to higher-order functions like List.map. It is interchangeable with a named let binding, let square x = x * x desugars to essentially the same underlying function value. fun supports pattern matching directly on its argument (fun (a, b) -> a + b) and multiple arguments via currying (fun x y -> x + y), and the function keyword is a shorthand for fun x -> match x with ... when the whole body is a single match.
Main execution block
let () = ... runs top-level side effects, matched against unit.
OCaml has no dedicated main function. A compiled program simply executes every top-level let binding in order as the module loads. Writing let () = ... is a convention: the pattern () (of type unit) forces the expression to also have type unit, so the compiler warns if a meaningful result is silently discarded. Multiple such blocks can appear throughout a file, each running as it is reached, which is why library modules can define values and functions freely while an executable's entry point is just the last expression of consequence.
Official OCaml site · All languages on AnatomyOf
The interactive tour needs JavaScript. Enable it to hover a callout and trace it into the code.