Skip to content
Part IA Michaelmas Term

Head, Tail, and Null

The null function

The function null tests whether a list is empty using pattern matching:

let null = function
  | [] -> true
  | x :: l -> false
(* val null : 'a list -> bool = <fun> *)

The two clauses match the two possible forms of a list: empty ([]) or non-empty (x :: l). The function returns true only for the empty list.

Head and tail access

hd returns the first element of a non-empty list; tl returns the list of remaining elements:

let hd (x :: l) = x
(* val hd : 'a list -> 'a = <fun> *)

let tl (x :: l) = l
(* val tl : 'a list -> 'a list = <fun> *)

Both functions raise a warning when compiled: the pattern matching is not exhaustive — the case [] is not handled. Applying hd or tl to [] raises an exception at runtime. The fix is to use null to check for emptiness beforehand, or better, to use proper two-case pattern matching.

Partial match warnings

OCaml’s compiler detects incomplete pattern matches. Two faulty declarations illustrate the problem:

(* Handles only empty lists -- non-empty case missing *)
let rec nlength [] = 0

(* Handles only non-empty lists -- empty case missing *)
let rec nlength (x :: xs) = 1 + nlength xs

These are two separate declarations. The second let replaces the first rather than extending it. To handle both cases, use the function keyword with | to separate clauses.

Polymorphic types

The primitive list functions have polymorphic types using type variables:

FunctionTypeMeaning
null'a list -> boolWorks on any list
hd'a list -> 'aHead matches element type
tl'a list -> 'a listTail is same type of list

The symbols 'a and 'b are type variables (pronounced “alpha”, “beta”, etc.) and stand for any type. Code using these functions is checked for type correctness at compile time, and this guarantees that at run time, all elements of a list have the same type.

Pattern matching over hd and tl

Taking a list apart using combinations of hd and tl is hard to get right and seldom necessary. Pattern matching with the function keyword is the preferred approach:

let rec process = function
  | [] -> base_case
  | x :: xs -> ... x ... process xs ...

This is safer (no missing cases if both are covered) and clearer (the structure of the list is visible in the pattern).