The OCaml Toolchain

opam manages the compiler and packages; dune builds projects. See OCaml Internals for the runtime and generated code.

opam and switches

A switch is an isolated compiler and package environment. A local switch keeps it under the project directory. Use the compiler version required by your course or project.

opam switch create . 5.2.0 --deps-only
eval $(opam env)
opam install dune ocaml-lsp-server ocamlformat utop odoc

The command above uses OCaml 5.2.0. opam install --dry-run shows proposed dependency changes. opam pin selects a particular source or version within the switch.

dune

dune-project sets the dune language version. Files named dune describe targets and their dependencies.

  • dune build @all builds all targets.
  • dune runtest runs tests.
  • dune build --watch rebuilds after changes.

Development and release profiles use different settings. Benchmark the profile you intend to deploy; optimisation also depends on the compiler build.

Looking at what the compiler produced

-dlambda shows the Lambda intermediate representation. -dcmm shows lower-level operations, including allocation. -S keeps native assembly output.

The Gc module exposes allocation and collection counters. Use measurements before changing optimisation flags or disabling checks.

Editors and formatting

Install ocaml-lsp-server in the switch and make sure your editor uses that environment. Dune supplies the project configuration.

Add a .ocamlformat file and agree on a formatter version to keep formatting consistent.

Keeping the environment straight

The shell, editor and build tool need to use the same switch. opam switch show reports the selected switch; opam exec -- runs a command inside it. An editor opened before a switch change may need restarting.

Keep package requirements with the project. Another person should be able to install dependencies and run the build without copying your switch directory.

Reading type errors

Find the first expression whose inferred type differs from the expected type. Check the surrounding function's arguments and return value before changing annotations. A type error reported late in a function can begin with an earlier branch.

All branches of a match must agree on their result type. Pattern matching can unpack a value and check its constructor together. Exhaustiveness warnings identify cases your code has not covered.

Project habits

Keep reusable code in a library and command-line entry points small. Put tests next to the behaviour they cover, and run dune runtest before sharing changes. Commit source and configuration; leave _build and local switch contents out of Git.

Use utop for short investigations, then move the result into a source file so it can be rebuilt and reviewed.

Reading