Lecture 1.3: Getting Started with OCaml; Calculator Expressions
Overview of Syllabus
The syllabus can be found here.
Setting up OCaml
Installation instructions are here. Alternative instructions are here.
For Mac and Linux:
- Run
bash -c "sh <(curl -fsSL https://opam.ocaml.org/install.sh)"
in a terminal. Press enter when it asks you where to install it, and enter your password when it asks you to. - Run
opam init
; when it asks you how to modifyzsh
, just press enter. - Run
opam install ocaml-lsp-server odoc ocamlformat utop
. If it says that no switch exists, first runopam switch create cs2800 ocaml-base-compiler.5.3.0
. - Try running
utop
. If that doesn't work, try first runningeval $(opam env)
. If it works after runningeval $(opam env)
, run this command so that you don't have to again:echo 'eval $(opam env)' >> ~/.zshrc
. - (Recommended) Install VSCode, search for "OCaml", and install the official OCaml Platform extension.
For Windows:
- Install Windows Subsystem for Linux. Open Powershell in administrator mode (right-click on PowerShell and select "Run and administrator"). Then, enter the following command:
wsl --install
- Once WSL finishes installing, you can open an Ubuntu terminal via the Start menu.
- Then, follow the instructions for Mac/Linux above. When you install VSCode, you also need to
First Steps: Arithmetic Expressions
There are two ways we will run OCaml: via utop
, OCaml's REPL
(Read-Eval-Print-Loop), which allows you to type in commands interactively; and
via ocamlc
, which compiles OCaml to lower-level code that can be executed.
We will begin with utop
.
In utop
, you can write and evaluate OCaml expressions:
The top line is our input 2+2;;
, while the bottom line shows the result. We must end commands to utop
with ;;
, because newlines are ignored in OCaml:
The bottom line shows utop
's response. This response contains two things: a type (here, int
), and a resulting value (unsurprisingly, we get 4
). The type int
is for integers: i.e., whole numbers. OCaml supports addition, multiplication, and integer division through the usual operators:
Integer division in OCaml (like many other languages) will round down to the nearest integer. You can get the remainder by using the infix operator mod
:
utop # 7 / 3;;
- : int = 2
utop # 7 mod 3;;
- : int = 1
utop # (7 / 3) * 3 + (7 mod 3);;
- : int = 7
Exponentiation
Exponentiation in OCaml is not built-in, but we will later be able to define it.
To quit utop
, type CTRL-D, or type #quit;;
.
Floating-Point Numbers
If you want to not only use whole numbers, you can use the float
type in OCaml, which denotes floating-point numbers.
You can think of these as numbers with decimals.
Note that the type for 3.2
is float
, not int
.
Arithmetic in OCaml is strongly typed, which means that arithmetic operators over int
do not work over float
. Let's try it out:
OCaml gives us a type error because 3.2
is a float
, but +
expects an int
on both sides.
To fix this, we use floating-point variations of each operator. For addition, that is +.
. Let's try that out:
utop # 3.2 +. 5;;
Error: The constant 5 has type int but an expression was expected of type float
Hint: Did you mean 5.?
+.
expects float
s on both sides, but 5
has type int
. The error message we get tells us that we should instead use 5.
, which is the floating-point literal for five:
utop # 3.2 +. 5.;;
- : float = 8.2
utop # 3.2 *. 5.;;
- : float = 16.
utop # 5.3 /. 5.;;
- : float = 1.06
Booleans
The two booleans in OCaml are written true
and false
. The operators between them are ||
(for OR), &&
(for AND) and not
:
utop # true;;
- : bool = true
utop # true || false;;
- : bool = true
utop # true && false;;
- : bool = false
utop # not true;;
- : bool = false
utop # not false || true;;
- : bool = true
not false || true
gets interpreted as (not false) || true
. Instead, we could have inserted parentheses to force the other interpretation:
Booleans can be inspected using if .. then .. else ..
:
Strings
Strings can be created by using double quotes:
Strings can be concatenated together (put side by side) by using the ^
operator (don't confuse it with exponentiation!)
Let expressions
Expressions can locally define variables using let x = .. in ..
:
These can be arbitrarily nested and sequenced:
Which can be written in a cleaner style by using newlines:
Let expressions can also be nested inside on the left-hand side of another let expression:
utop # let x =
let a = 1 in a + 1
in
x * 2;;
- : int = 4
utop # let x =
(let a = 1 in a) * (let x = 2 in x + 1) in
100 / x
;;
- : int = 33
In utop
, you can define variables without giving a right-hand side in ..
:
The binding of x
will be available for the rest of the utop
session. You can update the binding by re-defining it:
You can even make reference to the old version of x
while defining the new one:
x
! Instead, this is re-defining a new value of x
based on the old one.
Equality
Integers can be tested for equality using =
:
We can use =
for Booleans as well:
The =
operator will be useful for other types too; we will revisit this later.
Underscore variables
It will be useful to know later that you can ignore the result of a let
binding by naming the variable _
:
Editing OCaml in VSCode
Let's create tst.ml
in VSCode, and fill it with the following contents:
let x = 3
let y = x + 1
let _ =
(* Print the value of y *)
print_endline ("Value of y: " ^ string_of_int y)
;;
separator is not required in ml
files.
Also, see that comments are written in OCaml like (* ... *)
.
Now, hover over various variables to see their types. You should see, for example, that x
has type int
,
while "Value of y: "
has type string
. If you hover over +
, you will see it has type int -> int -> int
; this is the type of a function that takes two integers and returns an integer. You will also see some helpful documentation.
Similarly, if you hover over string_of_int
, you will see it has type int -> string
.
To run it, we have a few options:
- Use
ocamlc
to compile it: - Interpret it using
utop
: - Load it using
utop
, which will make the bindings available in the REPL:
Functions
So far, we have seen the basic OCaml types int
, bool
, and string
. Now, let's look at functions. OCaml, being a functional programming language, makes functions very easy to use and manipulate.
To create a function, we can use the syntax fun (x : t) -> e
, where t
is a type and e
is an expression:
Type annotations
We will see later in the class that type annotations can be optional. For now, we will use them throughout.
The value fun (x : int) -> x + 1
is an OCaml value, just as 3
or "hi"
is. However, unlike int
or string
, we get a function type, written t1 -> t2
, where t1
and t2
are types.
The <fun>
syntax is OCaml's way of printing the value of the function.
Functions can be applied to arguments by writing them next to each other (and inserting parentheses where needed):
Note that we needed to wrap the fuction in parentheses; if we didn't, we would get a type error:
utop # fun (x : int) -> x + 1 3;;
Error: This expression has type int
This is not a function; it cannot be applied.
This is because OCaml would parse this as fun x : int -> x + (1 3)
, and this would mean treating 1
as a function and applying it to the argument 3
.
Because functions are values, we can manipulate them just like other values, including let-binding them: