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 floats 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
ocamlcto 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: