Skip to content

OCaml Cheat Sheet

The purpose of this page is to provide a brief "cheat sheet" of the basics of OCaml that you will use in this class.

The REPL

You can use the REPL (Read/Eval/Print Loop) to interact with OCaml, including with files that you have written.

Entering Commands

OCaml can be used interactively with the utop REPL. Each full command you enter into utop must be ended with ;;.

utop# 2 + 2;;
- : int = 4

Contrast this with:

utop# 2 + 2 
3;;
Error: This expression has type int (highlighting the second 2)
       This is not a function; it cannot be applied.

This gives an error because it is parsed the same as 2 + 2 3.

Loading Files

You can load a file into utop by using #use:

test.ml
let foo = 3

utop# #use "test.ml";;
val foo : int = 42

If your file uses modules in other files, you will need to compile them first.

test2.ml
open Test
let bar = foo
> ocamlc -c test.ml

This will generate test.cmo, which you then will load by calling utop test.cmo:

utop# #use "test2.ml";;
val bar = 42

We use ocamlc -c to just compile the file (but not generate an executable). Note that OCaml always capitalizes the first letter of your library file: our file was test.ml, but when we use it in another file, we need to say open Test.

Running OCaml

If you just want to run OCaml outside of the REPL, you can call utop file.ml, which will interpret it, but not give you a REPL. Just like above, if your file uses another file, you will need to compile it first and then pass the .cmo file to utop.

mylib.ml
let foo = 42

file.ml
let bar = Mylib.foo
let _ = print_int bar; print_string "\n"
> ocamlc -c mylib.ml
> utop mylib.cmo file.ml
42

You can also use ocamlc to generate an executable:

> ocamlc mylib.cmo file.ml -o file
./file
42

Integers, Floats, Booleans, and Strings

Integers

Other than integer constants (e.g., 32), integers can be created using the usual arithmetic operators: +, *, /, and mod.

utop# (2 mod 2) + (3 * 5) mod 7;;
- : int = 1

The orinary comparison operators also work:

utop# 2 < 3;;
- : bool = true

utop# 3 <= 3;;
- : bool = true

Each infix operator (including the comparison operators) are actually OCaml functions:

utop# 2 + 2 ;;
- : int = 4

utop# (+);;
- : int -> int -> int = <fun>

utop# (+) 2 2;;
- : int = 4

Floats

Floating point numbers must be written with decimals (e.g., 2.0). You can write 2. as shorthand for 2.0. Arithmetic operators for floating point numbers are written with a . at the end: +., *., and /.. Comparison operators are the same as for integers:

utop# 2.0 < 3.0 +. 1.0;;
- : bool = true

Booleans

The boolean constants are true and false. The boolean operators are given by &&, ||, and not:

utop# false || not (true && true);;
- : bool = false

Other than pattern matching, you can also use if/then/else expressions for analyzing a bool:

utop# if (2 < 3) then "a" else "b";;
- : string = "a"

Strings and Chars

Strings can be concatenated with ^:

utop# "a" ^ "b";;
- : string = "ab"

A string is logically a sequence of chars, each of which stands for a single byte.

utop# 'a';;
- : char = 'a'

Function Types

The type t1 -> t2 is the type of OCaml functions from t1 to t2.

Top-level functions can be defined using let.

let foo (x : int) = print_int x


let bar (x : string) (y : string) = print_string (x ^ y) (* ^ is string concatenation *)

The type for bar above will be string -> string -> unit: you can either think of this as a two-argument function, or a function from string to functions of type string -> unit:

utop# let bar (x : string) (y : string) = print_string (x ^ y);;
val bar : string -> string -> unit = <fun>

utop# bar "hi" " there\n";;
hi there
- : unit = ()

utop# bar "hi";;
- : string -> unit = <fun>

Inline (anonymous) functions are created using fun:

utop# fun (x : int) -> x + 1;;
- : int -> int = <fun>

Recursive functions need to be defined via let rec.

let rec foo (x : int) = 
    if x = 0 then 0 else 
    1 + foo (x - 1)

Unit and Printing

The type unit, which only has the value (), is used for the return type of expressions that produce side effects such as printing a string:

utop# print_string "Hello, world!\n";;
Hello, world!
- : unit = ()

You can sequence programs by using e1 ; e2: this will evaluate e1, throw away its return value, and evaluate e2:

utop# print_string "Hello"; print_string " world!\n";;
Hello world!
- : unit = ()
utop# let _ = print_string "foo"; print_string "bar\n";;
foobar
- : unit = ()
utop# let x = (print_string "hi\n"; 42);;
hi
val x : int = 42

Lists

Creating lists is done with the notation [1; 2; 3]. You can append lists using @:

utop# [1; 2; 3] @ [4; 5; 6];;
- : int list = [1; 2; 3; 4; 5; 6]

You can add to the end of a list by using :::

utop# 1 :: [2; 3];;
- : int list = [1; 2; 3]

Pairs

utop# (1, 2);;
- : int * int = (1, 2)

utop# fst (1, 2);;
- : int = 1

utop# snd (1, 2);;
- : int = 2

Pattern Matching

For lists:

let rec sum (xs : int list) = 
    match xs with 
    | [] -> 0
    | x::xs' -> x + sum xs'

For pairs, we can pattern match on pairs of more than two elements:

let get_second (p : int * bool * string) : bool = 
    match p with 
    | (_, b, _) -> b

(See pattern matching on other types below.)

Defining functions via function

A convenient syntax exists for defining functions by pattern matching:

let rec sum = function
    | [] -> 0
    | x :: xs' -> x + sum xs'

It is preferable to use the more explicit match ... with syntax at first, though, until you are more expert with OCaml.

Type aliases, ADTs and Records

Type aliases

type t = int

let test : t = 3

ADTs

Algebraic data types can be defined as follows:

type bool_tree = | Leaf of bool | Branch of bool_tree * bool_tree

We then construct bool_tree values by using the constructors:

utop# Leaf false;;
- : bool_tree = Leaf false

utop# Branch (Leaf false, Leaf true);;
- : bool_tree = Branch (Leaf false, Leaf true)

We inspect values of ADTs via pattern matching:

let rec sum_tree (t : bool_tree) = 
    match t with 
    | Leaf b => if b then 1 else 0
    | Branch (t1, t2) -> sum_tree t1 + sum_tree t2

Note that constructors of ADTs are not quite the same as functions in OCaml; i.e., Leaf on its own is not a function of type bool -> bool_tree.

utop# Leaf;;
Error: The constructor Leaf expects 1 argument(s),                                                                                                                    |~
       but is applied here to 0 argument(s)

However, you can create functions to wrap these constructors:

utop# let leaf x = Leaf x;;
- : bool -> bool_tree = <fun>

utop# let branch x y = Branch (x, y);;
- : bool_tree -> bool_tree -> bool_tree = <fun>

Records

Records, similar to structs in other languages, let you package together data with convenient ways to access it:

type my_rec = { x : int; y : bool; z : bool } 

let tst : my_rec = { x = 3; y = false; z = true }

let tst_x : int = tst.x

Given a record, you can create another one by replacing some of its fields:

let tst2 : my_rec = { tst with x = 4; z = false }

State

The only state we will use in this class is ref. You can create a reference by using ref. If x has type int ref, then !x (dereference) has type int, and x := 3 (assignment) has type unit.

utop# let x = ref 3;;
val x : int ref = {contents = 3} (* Don't worry about what contents means for now. *)

utop# x := 2;;
- : unit = ()

utop# !x;;
- : unit = 2


utop# x := 2; x := 1; !x;;
- : int = 1

Anything can be stored in a ref, including functions and other refs.

utop# let x = ref (ref 2);;
val x : int ref ref = ...

utop# x := ref 3;;
- : unit = ()

utop# ! (! x);;
- : int = 3

utop# let y = ref (fun (x : int) -> x + 1);;
val y : (int -> int) ref = ...

utop# y := (fun (x : int) -> x * 2);;
- : unit = ()

utop# !y 4;;
- : int = 8

Polymorphism

Polymorphic Functions

If you write a function that is valid for multiple different types, a polymorphic type will be assigned to it.

utop# let f x y z = x;;
val f : 'a -> 'b -> 'c -> 'a = <fun>

Here, 'a/'b/'c are type variables, which stand in for any type. When you apply arguments to f, you constrain those type variables by equalities:

utop# f 32;;
- : '_weak1 -> '_weak2 -> int = <fun>

The other two type variables stay type variables (albeit weak ones, which we won't need to discuss), because f 32 doesn't constrain the type of the other two arguments. However, note that the return type of f 32 is int, because the signature of f states that the return type ('a) is the same as the type of the first argument.

You can also write f using type annotations (which I often do for documentation purposes):

utop# let g (x : 'a) (y : 'b) (z : 'b) = y;;
val g : 'a -> 'b -> 'b -> 'b = <fun>

Note here that we have used type variables to constrain the third argument to have the same type as the second one:

utop# g 32 "a" "b";;
- : string = "a"    

utop# g 32 "a" false;;
Error: This expression (false) has type bool but an expression was expected of type string

Why did we expect a string here? Because "a" caused the constraint 'b = string.

Polymorphic Types

Datatypes and type aliases can have type variables:

utop# type 'a t = 'a list;;
type 'a t = 'a list

utop# type 'a tree = | Leaf of 'a | Branch of 'a tree * 'a tree;;
type 'a tree = | Leaf of 'a | Branch of 'a tree * 'a tree

Indeed, you can think of list as being defined like below:

utop# type 'a my_list = | Nil | Cons of 'a * 'a my_list;;
type 'a my_list = Nil | Cons of 'a * 'a my_list

You can have multiple type variables, but they need to be expressed as a tuple:

utop# type ('a, 'b) either = | Left of 'a | Right of 'b;;
type ('a, 'b) either = | Left of 'a | Right of 'b

In this way, either is similar to a binary function on types. However, unlike normal functions, we cannot partially apply a single type to either.

Modules and Module Types

Given a file foo.ml, the collection of definitions in this file will be associated to a module Foo. If Foo contains a value (say, let x : int = 42), another module can access this value either via Foo.x, or by opening Foo:

client.ml
open Foo
let y = x

Other files, like client.ml above, can only use Foo if we compile foo.ml into foo.cmo (by calling ocamlc foo.ml -c), and then interpreting/compiling client.ml with foo.cmo: utop foo.cmo foo.ml.

Modules in OCaml can also be manipulated within files:

module Bar = struct 
    let z = "hello"
end

let w = Bar.z (* OK *)

(* Or, *)
open Bar
let w = z

Like how you can do type s = t, you can also give new names to old modules:

module M = Bar
... M.z ...

And, of course, you can embed modules in other modules:

module Bar = struct 
    module M = struct 
        let z = 4
    end
    let w = M.z
end

let a = Bar.w (* OK *)
let b = Bar.M.z (* Also OK *)

Module Types

The power of modules comes from module types, which hides information behind a module type.

module type S = sig 
    type t 
    val z : t
end

module M : S = struct 
    type t = int
    let z = 4
end

let x : M.t = M.z (* OK *)
let z : M.t = 3 (* Not OK *)
let y = x + 2 (* Not OK *)

Above, z and y fail to type check because the world outside of M knows that t is a type, and that z has type t, but nothing else. The outside world does not know that t = int. However, if we added this information to S, it would work:

module type S = sig
    type t = int
    val z : t
end

module M : S = struct (* ... *) end

let y = x + 2 (* OK *)

Importantly, M has to contain at least the definitions in S, but more are OK too:

module M : S = struct
    type t = int
    type s = t option
    let z = 3
    let x : s = Some z
end

Functors

A functor in OCaml is a function from modules to modules. We specify functors via a signature that takes other modules, along with their module types, as parameters.

module type Eq = sig
    type t
    val eq : t -> t -> bool
end

module Foo (M : Eq) = struct
    let f (x : M.t) (y : M.t) = not (M.eq x y)
end

Sub-modules

A powerful feature of modules, when combined with functors, is that smaller module types can serve as specifications of larger modules:

module type Eq = sig
    type t
    val eq : t -> t -> bool
end

module MkFoo (M : Eq) = struct
    let f (x : M.t) (y : M.t) = not (M.eq x y)
end


module MyType = struct
    type t = | TInt of int | TString of string

    let eq (x : t) (y : t) = 
        match (x, y) with
        | (TInt a, TInt b) -> a = b
        | (TString a, TString b) -> a = b
        | _ -> false

    let print (x : t) : unit = 
        match x with 
        | TInt i -> print_int i
        | TString i -> print_string i

    (* many more ops.. *)
end

module Foo = MkFoo(MyType) (* OK, because MyType adheres to Eq *)