Skip to content
Part IA Michaelmas Term

List Type and Constructors

List syntax

A list is an ordered series of elements; repetitions are significant. So [3; 5; 9] differs from [5; 3; 9] and from [3; 3; 5; 9]. Elements are separated with ; when constructing, as opposed to , syntax used for fixed-length tuples.

[3; 5; 9]                     (* int list *)
[(1, "one"); (2, "two")]      (* (int * string) list *)
[[3]; []; [5; 6]]             (* int list list *)

Type homogeneity

All elements of a list must have the same type. If x1,,xnx_1, \dots, x_n all have type τ\tau, then [x1; ...; xn] has type τ\tau list.

This contrasts with tuples, where (3, "hello") has type int * string and the elements may differ. The homogeneity property is checked at compile time, guaranteeing that no type errors occur at runtime when processing lists.

Lists of lists

Lists can contain lists as elements, forming nested structures:

let nested = [[1; 2]; [3]; []; [4; 5; 6]]
(* val nested : int list list *)

The empty list [] can appear as an element of a list-of-lists. Each inner list is independently sized.

concat (@) and List.rev

The infix operator @ (also called List.append) concatenates two lists:

[3; 5; 9] @ [2; 10]    (* [3; 5; 9; 2; 10] *)

List.rev reverses a list:

List.rev [(1, "one"); (2, "two")]    (* [(2, "two"); (1, "one")] *)

Two kinds of lists

There are exactly two kinds of list values:

ConstructorMeaningExample
[]The empty list (nil)[]
x :: lList with head x and tail l1 :: [2; 3]

Every list is either empty, or a head element attached to a tail list. This recursive definition is the foundation of all list processing — functions over lists follow the same two-case structure:

let rec f = function
  | [] -> ...        (* handle empty case *)
  | x :: xs -> ...   (* handle head x, recursive call on tail xs *)