The OCaml Toplevel
2.1. The OCaml Toplevel#
The toplevel is like a calculator or command-line interface to OCaml. It’s similar to JShell for Java, or the interactive Python interpreter. The toplevel is handy for trying out small pieces of code without going to the trouble of launching the OCaml compiler. But don’t get too reliant on it, because creating, compiling, and testing large programs will require more powerful tools. Some other languages would call the toplevel a REPL, which stands for read-eval-print-loop: it reads programmer input, evaluates it, prints the result, and then repeats.
In a terminal window, type
utop to start the toplevel. Press Control-D to exit
the toplevel. You can also enter
#quit;; and press return. Note that you must
# there: it is in addition to the
# prompt you already see.
2.1.1. Types and values#
You can enter expressions into the OCaml toplevel. End an expression with a
;; and press the return key. OCaml will then evaluate the
expression, tell you the resulting value, and the value’s type. For example:
# 42;; - : int = 42
Let’s dissect that response from utop, reading right to left:
42is the value.
intis the type of the value.
The value was not given a name, hence the symbol
That utop interaction was “hardcoded” as part of this book. We had to type in
all the characters: the
-, etc. But the infrastructure used to write
this book actually enables us to write code that is evaluated by OCaml at the
time the book is translated into HTML or PDF. From now on, that’s usually what
we will do. It looks like this:
- : int = 42
The first code block with the
42 in it is the code we asked OCaml to run. If
you want to enter that into utop, you can copy and paste it. There’s an icon in
the top right of the block to do that easily. Just remember to add the double
semicolon at the end. The second code block, which is indented a little, is the
output from OCaml as the book was being translated.
If you’re viewing this in a web browser, look to the top right for a download
icon. Choose the
.md option, and you’ll see the original
MyST Markdown source code for this page of the book. You’ll see that the
output from the second example above is not actually present in the source code.
That’s good! It means that the output stays consistent with whatever current
version of the OCaml compiler we use to build the book. It also means that any
compilation errors can be detected as part of building the book, instead of
lurking for you, dear reader, to find them.
You can bind values to names with a
let definition, as follows:
let x = 42
val x : int = 42
Again, let’s dissect that response, this time reading left to right:
A value was bound to a name, hence the
xis the name to which the value was bound.
intis the type of the value.
42is the value.
You can pronounce the entire output as “
x has type
int and equals
A function can be defined at the toplevel using syntax like this:
let increment x = x + 1
val increment : int -> int = <fun>
Let’s dissect that response:
incrementis the identifier to which the value was bound.
int -> intis the type of the value. This is the type of functions that take an
intas input and produce an
intas output. Think of the arrow
->as a kind of visual metaphor for the transformation of one value into another value—which is what functions do.
The value is a function, which the toplevel chooses not to print (because it has now been compiled and has a representation in memory that isn’t easily amenable to pretty printing). Instead, the toplevel prints
<fun>, which is just a placeholder.
<fun> itself is not a value. It just indicates an unprintable function value.
You can “call” functions with syntax like this:
- : int = 1
- : int = 22
increment (increment 5)
- : int = 7
But in OCaml the usual vocabulary is that we “apply” the function rather than “call” it.
Note how OCaml is flexible about whether you write the parentheses or not, and
whether you write whitespace or not. One of the challenges of first learning
OCaml can be figuring out when parentheses are actually required. So if you find
yourself having problems with syntax errors, one strategy is to try adding some
parentheses. The preferred style, though, is usually to omit parentheses when
they are not needed. So,
increment 21 is better than
2.1.3. Loading code in the toplevel#
In addition to allowing you to define functions, the toplevel will also accept
directives that are not OCaml code but rather tell the toplevel itself to do
something. All directives begin with the
# character. Perhaps the most common
#use, which loads all the code from a file into the toplevel,
just as if you had typed the code from that file into the toplevel.
For example, suppose you create a file named
mycode.ml. In that file put the
let inc x = x + 1
Start the toplevel. Try entering the following expression, and observe the error:
File "", line 1, characters 0-3: 1 | inc 3 ^^^ Error: Unbound value inc Hint: Did you mean incr?
The error occurs because the toplevel does not yet know anything about a
inc. Now issue the following directive to the toplevel:
# #use "mycode.ml";;
Note that the first
# character above indicates the toplevel prompt to you.
# character is one that you type to tell the toplevel that you are
issuing a directive. Without that character, the toplevel would think that you
are trying to apply a function named
Now try again:
- : int = 4
2.1.4. Workflow in the toplevel#
The best workflow when using the toplevel with code stored in files is:
Edit the code in the file.
Load the code in the toplevel with
Interactively test the code.
Exit the toplevel. Warning: do not skip this step.
Suppose you wanted to fix a bug in your code. It’s tempting to not exit the
toplevel, edit the file, and re-issue the
#use directive into the same
toplevel session. Resist that temptation. The “stale code” that was loaded from
#use directive in the same session can cause surprising things to
happen—surprising when you’re first learning the language, anyway. So
always exit the toplevel before re-using a file.