OCaml Module 1 – Start here: OCaml, opam, dune and the toplevel
Install OCaml 5.5.1 with opam and use the REPL
Install opam 2.6.0 and an OCaml 5.5.1 switch, use the toplevel with ;; phrases and its directives, run commands in a switch and avoid a macOS SDK trap.
What you will learn
- Install opam 2.6.0, initialise it and create a global or project-local 5.5.1 switch
- Use the ocaml toplevel and utop with ;; phrases and the
- Recognise and work around known platform traps, including the macOS 27 SDK Unix.pipe crash
- Run commands inside a switch with opam exec and opam env
Before you start
What you’ll learn · Before you start
What you will learn
- Install opam 2.6.0, initialise it and create a global or project-local 5.5.1 switch
- Use the ocaml toplevel and utop with ;; phrases and the
- Recognise and work around known platform traps, including the macOS 27 SDK Unix.pipe crash
- Run commands inside a switch with opam exec and opam env
Before you start
On this page
Typing 0.1 +. 0.2;; into OCaml’s toplevel prints 0.300000000000000044, while string_of_float (0.1 +. 0.2) gives
"0.3": the same number, shown two ways. Before you can try that yourself you need OCaml installed, and the usual way
is opam, which keeps each compiler and its packages in a switch of their own.
Below you will follow the setup from installing opam to a working OCaml 5.5.1 switch, type phrases into the toplevel and read its answers, use directives to load files, trace calls and load libraries, choose which switch a command uses, and recognise one known macOS trap before it costs you an afternoon.
opam installs OCaml into switches, one compiler and its packages in each
opam is OCaml’s package manager. It installs compilers and libraries into a
opam switch: One OCaml compiler together with its own set of installed packages, kept apart from every other switch; global switches live in the opam root and local ones in a project's _opam folder.: one OCaml compiler together with its own set of packages, kept apart from every
other switch. The ocaml.org installation guide gives one command to get opam on each system, brew install opam on
macOS, winget install Git.Git OCaml.opam on Windows, and an install script on Linux, followed by opam init. Follow
the whole setup, stage by stage:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- Get opam itself. Install the opam package manager with Homebrew on macOS, WinGet on Windows or the official install script on Linux, as the ocaml.org installation guide describes.
- Initialise opam once. opam init creates the opam root, ~/.opam, and downloads the list of packages from the opam repository. It asks to add a hook to your shell's startup file.
- Create a switch. A switch is one compiler with its own set of packages. opam switch create 5.5.1 builds OCaml 5.5.1 into ~/.opam/5.5.1, which takes several minutes.
- Or a switch per project. Run inside a project folder, opam switch create . 5.5.1 makes a local switch in the folder's _opam directory, so that project's packages stay apart from every other.
- Use the switch in this shell. eval $(opam env) sets PATH and the other variables for the current switch in the shell you are typing into. opam exec -- runs a single command the same way instead.
- Check the result. ocaml -version should now print version 5.5.1. If it prints another version or nothing, the switch is not on your PATH yet.
A global switch, opam switch create 5.5.1, lives in the opam root and can be used from anywhere. A local switch,
opam switch create . 5.5.1 run inside a project folder, lives in that folder’s _opam directory, which keeps one
project’s library versions from disturbing another’s. Here is what opam reports on the computer that records this
track, after opam init and opam switch create 5.5.1:
from session import run
# What opam knows after "opam init" and "opam switch create 5.5.1": the switch, its compiler and how to use it.
run("""
opam --version
opam switch list
opam switch show
opam exec -- ocaml -version
opam env --shell=sh | grep OPAM_SWITCH_PREFIX
""") Output
$ opam --version 2.6.0 $ opam switch list # switch compiler description → 5.5.1 ocaml-base-compiler.5.5.1,ocaml-options-vanilla.1 ocaml-base-compiler = 5.5.1 $ opam switch show 5.5.1 $ opam exec -- ocaml -version The OCaml toplevel, version 5.5.1 $ opam env --shell=sh | grep OPAM_SWITCH_PREFIX OPAM_SWITCH_PREFIX='~/.opam/5.5.1'; export OPAM_SWITCH_PREFIX;
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 switch_session.py
There is one switch, 5.5.1, and the arrow marks it as the current one. Its compiler package is
ocaml-base-compiler.5.5.1, the plain compiler with no special options.
What opam 2.6.0 changed for your shell
From opam 2.6.0, the shell hook updates the opam folder in PATH in place instead of always moving it to the front,
so a folder you put first yourself stays first. The release also deletes build folders sooner to save disk space.
The toplevel reads phrases and answers with each value’s type
The toplevel, which you start by typing ocaml, is OCaml’s read-eval-print loop. You type a
phrase: A definition or expression typed into the OCaml toplevel and ended with ;;, which the toplevel answers with a name, a type and a value., a definition or an expression ended by two semicolons, and it answers with
the name, the type and the value. Here are four phrases and their answers:
from session import toplevel
# Phrases end with ;; and the toplevel answers each with a name, a type and a value.
toplevel("""
let x = 6 * 7;;
x + 1;;
0.1 +. 0.2;;
string_of_float (0.1 +. 0.2);;
""") Output
$ ocaml OCaml version 5.5.1 Enter #help;; for help. # let x = 6 * 7;; val x : int = 42 # x + 1;; - : int = 43 # 0.1 +. 0.2;; - : float = 0.300000000000000044 # string_of_float (0.1 +. 0.2);; - : string = "0.3"
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 phrases_session.py
let x = 6 * 7;; defined a name, so the answer starts with val x; x + 1;; was a plain expression, so the answer
starts with -. The float sum printed as 0.300000000000000044 because the toplevel shows enough digits to identify
the exact binary value stored, while string_of_float rounds to a short form for people. The ;; is needed only to
tell the toplevel that you have finished typing; in a source file it is usually left out.
Key idea
End each phrase with ;;, then read the answer from left to right: the name (or -), the type after the colon and
the value after the equals sign.
Checkpoint
Check: opam and switches · Phrases and the toplevel's answers
Directives load files, show signatures, trace calls and load libraries
Lines that start with # are directive: An instruction to the OCaml toplevel itself that starts with #, such as #use to run a file, #trace to show calls or #require to load a library.: instructions to the toplevel itself,
not OCaml code. #use "file.ml" reads and runs a source file as if you had typed it. #trace f prints every call of
f with its argument and every return with its result. #show List.rev prints a name’s type or signature. And
#use "topfind" loads findlib, after which #require "re" loads an installed opam library:
from session import files, toplevel
# Directives start with # and talk to the toplevel itself: load a file, trace a function, show a type, load a library.
files("fact.ml")
toplevel("""
#use "fact.ml";;
#trace fact;;
fact 3;;
#show List.rev;;
#use "topfind";;
#require "re";;
Re.execp (Re.compile (Re.Pcre.re "^[0-9]+$")) "2026";;
""") Output
$ ocaml OCaml version 5.5.1 Enter #help;; for help. # #use "fact.ml";; val fact : int -> int = <fun> # #trace fact;; fact is now traced. # fact 3;; fact <-- 3 fact <-- 2 fact <-- 1 fact --> 1 fact --> 2 fact --> 6 - : int = 6 # #show List.rev;; val rev : 'a list -> 'a list # #use "topfind";; - : unit = () Findlib has been successfully loaded. Additional directives: #require "package";; to load a package #list;; to list the available packages #camlp4o;; to load camlp4 (standard syntax) #camlp4r;; to load camlp4 (revised syntax) #predicates "p,q,...";; to set these predicates Topfind.reset();; to force that packages will be reloaded #thread;; to enable threads - : unit = () # #require "re";; ~/.opam/5.5.1/lib/re: added to search path ~/.opam/5.5.1/lib/re/re.cma: loaded # Re.execp (Re.compile (Re.Pcre.re "^[0-9]+$")) "2026";; - : bool = true
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 directives_session.py
The trace makes recursion visible: fact 3 called fact 2, which called fact 1, and the results came back as 1, 2
and 6. After #require "re", the regular expression library was on the search path and Re.execp answered true
for “2026”.
utop, version 2.17.0 in this track’s pins, is an enhanced toplevel with line editing, history and completion. It
accepts the same phrases and directives, so everything in this section works in it too; install it with
opam install utop and start it with utop.
Keep a scratch file next to the toplevel
Write longer definitions in a file and load them with #use "scratch.ml". Editing the file and loading it again is
faster than retyping a function line by line.
Checkpoint
Check: Toplevel directives
opam exec and opam env choose the switch a command uses
A shell knows nothing about switches until opam tells it. eval $(opam env) sets PATH and a few other variables
for the current switch in the shell you are typing into, so ocaml, dune and the rest come from that switch until
you change it. opam exec -- COMMAND runs one command in the switch without changing your shell, which is the safer
choice inside scripts. opam switch show names the current switch, and opam list shows the packages installed
in it, which is the first thing to check when a library seems to be missing.
The session in the first section used both: opam exec -- ocaml -version reported 5.5.1, and opam env printed
OPAM_SWITCH_PREFIX='~/.opam/5.5.1', one of the variables it sets. When several switches exist, opam switch SWITCH changes the default, and a local _opam switch is chosen automatically inside its project folder when opam’s
shell hook is installed.
Running a script against the wrong compiler
Wrong: a build script that calls ocaml and assumes the right switch is on PATH. Right: opam exec --switch=5.5.1 -- dune build, or a local switch in the project, so the script uses the same compiler wherever it runs.
One known trap: a macOS SDK that breaks Unix.pipe
Some problems come from the platform rather than from OCaml. On macOS 26 with Command Line Tools whose default SDK is
the macOS 27 one, an OCaml 5.5.1 built from source detects the functions pipe2 and dup3, which that SDK marks as
available only from macOS 27. The upstream issue 15067 reports the result: Unix.pipe crashes, and so do builds that
depend on it. The fix, pull request 15071, is merged for the 5.5.2 bug-fix release.
Until then, the workaround is to build the switch with SDKROOT set to the macOS 26 SDK, as the switch of this track
was. This four-line program is the check: it opens a pipe and closes both ends.
(* Opens a pipe and closes both ends: the four-line check for the macOS SDK trap. *)
let () =
let r, w = Unix.pipe () in
Unix.close r;
Unix.close w;
print_endline "Unix.pipe works" from session import files, run
# The Unix.pipe check, built against the unix library with ocamlfind, on this switch (built with the macOS 26 SDK).
files("pipe_check.ml")
run("""
ocamlfind ocamlopt -package unix -linkpkg pipe_check.ml -o pipe_check
./pipe_check
""") Output
$ ocamlfind ocamlopt -package unix -linkpkg pipe_check.ml -o pipe_check $ ./pipe_check Unix.pipe works
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 pipe_session.py
On this switch it prints “Unix.pipe works”. On an affected build it would crash instead of printing, which tells you to rebuild the switch with the older SDK or to move to 5.5.2 when it is released.
Checkpoint
Check: opam exec and opam env · The macOS SDK trap
Practise: switches, phrases and directives
Answer from what the sessions showed. A question about a concept you missed comes back later in the same session, and each answer explains itself:
Practice that adapts to you
Practise this lesson
10 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up (fresher to 2–5 years): what is an opam switch, and when would you use a local one?
An opam switch is one OCaml compiler together with its own set of installed packages, kept separate from every other
switch on the machine. opam switch create 5.5.1 makes a global switch in the opam root, usable from anywhere, and
opam switch list shows which switches exist and which is current. A local switch, created with opam switch create . 5.5.1 inside a project folder, lives in that folder’s _opam directory. Use a local switch when a project needs its
own compiler or library versions, so that upgrading a library for one project cannot break another, and so that
everyone who builds the project gets the same set. Run commands in a switch with opam exec --, or set up the current
shell with eval $(opam env).
Two harder questions follow, about a build that uses the wrong compiler and about a crash that only one machine sees. Note your answer first, then open each first step:
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Mid to senior
A project builds on your laptop, but the continuous integration job fails with an error about a library version you never installed there. You suspect the job uses a different compiler or switch. How do you find out and fix it?
-
Mid to senior
A program that uses Unix.pipe crashes with a segmentation fault on one developer's Mac but runs on everyone else's, all with OCaml 5.5.1. How would you narrow it down without guessing?
Flashcards · 7 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
What is an opam switch?
One OCaml compiler together with its own installed packages, kept apart from every other switch on the machine. opam switch create 5.5.1 makes a global one and opam switch list shows them all.
-
Where does a local opam switch live, and how is it created?
In the project folder's _opam directory. Run opam switch create . 5.5.1 inside the project, so the project carries its own compiler and packages.
-
How do you read the toplevel's answer to a phrase?
End the phrase with ;;, then read left to right: the name (a dash when the value has none), the type after the colon and the value after the equals sign.
-
What do the directives #use, #trace, #show and #require do?
#use "file.ml" runs a source file as if typed, #trace f prints each call and return of f, #show prints a name's type or signature, and after #use "topfind", #require loads an installed library. utop accepts them too.
-
How do eval $(opam env) and opam exec -- differ?
eval $(opam env) puts a switch on the current shell's PATH; opam exec -- runs one command in a switch, and --switch=5.5.1 names which one, which suits scripts.
-
What did opam 2.6.0 change about the shell hook and PATH?
The hook now updates opam's folder in PATH where it already is instead of always moving it to the front, so a folder you put first stays first. The release also deletes build folders sooner.
-
Which macOS trap breaks a source-built OCaml 5.5.1?
Built with the macOS 27 SDK, it crashes in Unix.pipe. Building it with the macOS 26 SDK avoids the crash until 5.5.2 fixes it.
Key takeaways
- opam installs OCaml into switches, each one compiler with its own packages, created globally with
opam switch create 5.5.1or per project in an_opamfolder. - The toplevel reads phrases ending in
;;and answers with a name or-, a type and a value, showing floats with every digit needed. - Directives such as
#use,#trace,#showand#requireact on the toplevel itself, and utop accepts the same ones with better editing. eval $(opam env)puts a switch on the current shell’sPATH, whileopam exec --runs one command in a switch.- A macOS 27 SDK makes a source-built OCaml 5.5.1 crash in
Unix.pipe, and building with the macOS 26 SDK avoids it until 5.5.2.
Terms in this lesson
- directive
- An instruction to the OCaml toplevel itself that starts with #, such as #use to run a file, #trace to show calls or #require to load a library.
- opam switch
- One OCaml compiler together with its own set of installed packages, kept apart from every other switch; global switches live in the opam root and local ones in a project's _opam folder.
- phrase
- A definition or expression typed into the OCaml toplevel and ended with ;;, which the toplevel answers with a name, a type and a value.
References
- Installing OCaml (OCaml.org)
- opam 2.6.0 release (opam)
- opam manual (opam)
- Introduction to the OCaml toplevel (OCaml.org)
- The toplevel system or REPL (ocaml) (Inria and the OCaml developers)
- Fix dup3, pipe2 detection with macOS 27 SDK leading to a crash (issue 15067) (The OCaml developers)
- Fix for dup3, pipe2 detection as future macOS functions (pull request 15071) (The OCaml developers)
- utop (ocaml-community)
Report a problem with this lesson
Kept only in this browser. Your Learn progress