Pattern Matching

All about pattern matching in Ocaml.


Powerful features in OCaml, allowing to destructure values by matching them against patterns.

This feature is particularly useful when working with complex data types such as tuples, lists, and variants.

Syntax

In OCaml, the match expression is used to perform pattern matching. The syntax is as follows:

match expr with
| pattern1 -> result1
| pattern2 -> result2
| _ -> default_result
  • The expr is evaluated, and its value is compared to each pattern.
  • If a pattern matches the value, the corresponding result is returned.
  • The _ pattern acts as a "catch-all" for any unmatched cases.

Example

let classify_number n =
  match n with
  | 0 -> "Zero"
  | 1 -> "One"
  | _ -> "Other"

This function matches the value of n and returns the appropriate string.

Patterns

1. Constant Patterns

You can match specific constants such as integers, booleans, and characters.

match true with
| true -> "Yes"
| false -> "No"

2. Variable Patterns

You can bind a value to a variable in a pattern. This is useful when you don't need to match specific values but want to extract them.

let describe_number n =
  match n with
  | 0 -> "Zero"
  | n -> "The number is " ^ string_of_int n

In the second case, the pattern n matches any number and binds it to the variable n.

3. Tuple Patterns

You can match on tuples by breaking them down into their components.

let add_tuple t =
  match t with
  | (a, b) -> a + b
 
let result = add_tuple (3, 4)  (* Returns 7 *)

4. List Patterns

Lists can be matched using [] for the empty list or the :: (cons) operator to split the head and tail of a list.

let rec sum_list lst =
  match lst with
  | [] -> 0
  | head :: tail -> head + sum_list tail
 
let result = sum_list [1; 2; 3; 4]  (* Returns 10 *)

Here, head :: tail matches a non-empty list, where head is the first element and tail is the rest of the list.

5. Record Patterns

Record fields can be matched directly by specifying the field names. You can match all fields or only a subset.

type person = { name : string; age : int }
 
let describe_person p =
  match p with
  | { name = "Alice"; age = _ } -> "This is Alice"
  | { name; age } -> name ^ " is " ^ string_of_int age ^ " years old"

6. Variant Patterns

Pattern matching is particularly useful for working with variant types, also known as algebraic data types.

type shape =
  | Circle of float
  | Rectangle of float * float
 
let area s =
  match s with
  | Circle radius -> 3.1415 *. radius *. radius
  | Rectangle (width, height) -> width *. height

7. As Patterns

You can use as patterns to give a name to the entire value being matched, while also breaking it down into its components.

let rec print_list lst =
  match lst with
  | [] -> ()
  | (head :: tail) as full_list ->
      print_endline ("Full list: " ^ String.concat ", " (List.map string_of_int full_list));
      print_list tail

8. Or Patterns

You can use | to match multiple patterns at once.

let is_weekend day =
  match day with
  | "Saturday" | "Sunday" -> true
  | _ -> false

9. Wildcard Pattern (_)

The underscore (_) is a wildcard pattern that matches any value. It is often used as a catch-all case in pattern matching.

match n with
| 0 -> "Zero"
| 1 -> "One"
| _ -> "Other"

Pattern Matching with Guards

You can add extra conditions to a pattern match using guards (when clauses). This allows you to further refine the match based on the value.

let classify_number n =
  match n with
  | n when n > 0 -> "Positive"
  | n when n < 0 -> "Negative"
  | _ -> "Zero"

Here, when is used to check additional conditions beyond just matching a pattern.

Exhaustiveness and Warnings

OCaml will warn you if your pattern matching is not exhaustive, meaning that there are possible inputs for which the match doesn't handle. It is good practice to always include a _ case or ensure you cover all possible values.

let handle_option o =
  match o with
  | Some value -> value
  | None -> 0  (* Handles all cases of the 'option' type *)

Nested Pattern Matching

You can nest patterns to match deeply structured data.

type nested_list = 
  | Element of int
  | List of nested_list list
 
let rec sum_nested lst =
  match lst with
  | Element x -> x
  | List xs -> List.fold_left (fun acc x -> acc + sum_nested x) 0 xs
 
let result = sum_nested (List [Element 1; List [Element 2; Element 3]; Element 4])  (* Returns 10 *)

Pattern Matching with Exceptions

Pattern matching can also be used to handle exceptions.

let safe_divide x y =
  try x / y
  with Division_by_zero -> 0

In this example, the try ... with construct catches the Division_by_zero exception and returns a default value.

Pattern Matching on Options

The option type in OCaml is commonly used to represent values that may or may not exist, and pattern matching is a convenient way to work with it.

let get_default opt default_value =
  match opt with
  | Some value -> value
  | None -> default_value

This function returns the value inside an option, or a default value if it is None.