Benefits of OCaml

Pattern matching

Most languages provide a construct that allows the flow of execution to take one of several paths depending on the value of an expression. OCaml provides a construct known as pattern matching that is more powerful for two main reasons:

  • Patterns can depend upon the structure of a value, rather than just the constant value itself.
  • Patterns can bind variables to parts of a value.

Pattern matching is a large subject. Let us examine some of the pattern matching capabilities offered by OCaml.

Static checking

The OCaml compilers check pattern matches at compile-time to ensure that they are exhaustive (all possible inputs match at least one pattern, i.e. no values "escape" the pattern match) and contain no redundant patterns. Any inexhaustive or redundant patterns incur a warning (rather than an error).

For example, the following function tests for the empty list but does not provide an alternative for non-empty lists:

# let is_empty = function
    | [] -> true;;
Warning: this pattern-matching is not exhaustive.
Here is an example of a value that is not matched:
_::_
val is_empty : 'a list -> bool = <fun>

Note that the compiler provides an example _::_ of a value that is not matched by any pattern (in this case, any list with at least one element).

The following function tests for an empty list, returning true for the empty list, false for any list with at least one element and false otherwise:

# let is_empty = function
    | [] -> true
    | _::_ -> false
    | _ -> false;;
Warning: this match case is unused.
val is_empty : 'a list -> bool = <fun>

As the first two patterns match all possible lists, the third pattern is redundant and the compiler emits a warning about it.

Binding variables

Pattern matching is not only used to perform an action based upon the structure of a value, such as the emptiness of a list, it can also be used to dissect data structures by binding variables to parts of the data structure.

For example, the following function sums the integers in a list by adding the head to the sum of the tail:

# let rec sum list = match list with
    | [] -> 0
    | head::tail -> head + sum tail;;
sum : int list -> int = <fun>
# sum [1; 2; 3];;
- : int = 6

The second pattern matches any list with at least one element, binding the variable head to the first element of the list and the variable tail to the list containing the remaining elements. These two variables are used in the corresponding expression. By applying the sum function to the tail list, the function decomposes the list an element at a time.

Parallel pattern matching

The ability to dissect multiple data structures simultaneously can be of great benefit. This is known as parallel pattern matching and is implemented by simply matching over a tuple of the data structures.

For example, the following recursive function tests if every pair of elements in a pair of lists sum to zero:

# let rec f list1 list2 = match list1, list2 with
    | h1::t1, h2::t2 -> h1+h2=0 && f t1 t2
    | _, _ -> true;;
val f : int list -> int list -> bool = <fun>

Parallel pattern matching is one of the more powerful features of pattern matching and allows related data structures to be decomposed simultaneously. Moreover, parallel pattern matches are also compiled into efficient code.

Or patterns

Multiple patterns may invoke the same expression. For example, the following function returns true for lists with zero or one elements and false otherwise:

# let f = function
    | [] | [_] -> true
    | _::_::_ -> false;;
val f : 'a list -> bool = <fun>
# f [3];;
- : bool = true

The patterns [] and [_] both invoke the expression true.

OCaml extends this core functionality to allow or-patterns in subpatterns. For example, the following function counts the number of 0s and 1s in the given list:

# let rec count = function
    | [] -> 0
    | (0|1)::t -> 1 + count t
    | _::t -> count t;;
val count : int list -> int = <fun>
# count [1; 2; 3; 0; 1; 2; 3];;
- : int = 3

The subpattern 0|1 matches either 0 or 1.

Guarded patterns

In OCaml, patterns may have arbitrary boolean guard expressions associated with them, such that they can only match when the guard expression evaluates to true.

For example, the following function counts the number of 0s and 1s in the given list using a guard expression:

# let rec count = function
    | [] -> 0
    | h::t when h=0 || h=1 -> 1 + count t
    | _::t -> count t;;
val count : int list -> int = <fun>
# count [1; 2; 3; 0; 1; 2; 3];;
- : int = 3

Pattern matching is one of the most useful features of OCaml. In addition to providing an elegant and efficient means to dispatch computation, pattern matches are subjected to static checks beyond static type checking. Thus, pattern matching accelerates development and improves both reliability and performance.