Pattern-matching

Pattern-matching is a powerful way to do structural deconstruction of complex values. A “pattern” is a language construct that can be used on the left-hand side of let expressions, as well as the match expressions that we will see shortly, to suggest the form of the value and to pick out parts of that value.

You have already used pattern-matching without knowing it. For instance when we wrote:

let f (x, y) = x + y
f (1, 2)

we were actually using pattern-matching. We placed (x, y) in place of the parameter, and what we effectively told OCAML is “I expect this function to be called with the argument being a pair, and I want you to bind x to the first component of the pair and y to the second component when you evaluate the function body”. Later on when the call f (1, 2), OCAML will compare the value (1, 2) to the pattern (x, y), it will see that the formats match, and it will bind x to 1 and y to 2.

A similar example would be:

let (x, y) = (1, 2) in x + y;;

Pattern definition

We will start by describing the forms that a “pattern” can take. We will then discuss how a pattern matches a value. A pattern can be:

Here then are some examples of patterns:

(_, x :: rest)
(x, (y, z))
(None, x)
(Some (x, y), z)
(Some x) :: rest

Pattern matching a value

When the program is evaluated, the question that will arise is whether a pattern p matches a value v, and what changes to the dynamic environment this would elicit. Here are the rules for that:

Let us practice this a bit. Which of the following patterns match the corresponding values? What bindings do they produce? After you answer it on the board, test it on the OCAML interpreter by typing let pattern = value;;.

  1. pattern (Some _, x), value (Some 3, (1, 2)).
  2. pattern x :: rest, value [1; 2; 3].
  3. pattern x :: rest, value [].
  4. pattern x :: [], value [1].
  5. pattern x :: y :: [], value [(1, 3); (3, 2)].
  6. pattern (None, Some x), value (Some 3, Some 2).

The match-with construct

The match-with construct offers us a powerful way to deconstruct values by using patterns. Its syntax is as follows:

match e with
| p1 -> e1
| p2 -> e2
| p3 -> e3
...

A match-with expression is evaluated as follows:

The typechecking is a little more complicated, so we will omit all the details, but the bottom line is that the expressions e1, e2, e3, ..., must all have the same type. Also the type of e must be consistent with the kinds of types that the patterns expect. The system looks at all the patterns and produces one common type from them, then expects e to have that type.

Let us look at some examples:

match e with
| Some (x, y) -> x + y
| None -> 0

Let us think of what type information is contained in the above code:

Here are some examples of what would happen for various values of e:

As further practice, think through the type considerations in the following code:

match e with
| Some (Some x) -> x
| Some _ -> 1
| None -> 0

The importance of pattern-matching will become clear when we combine it with recursion to offer expressive ways of processing functions.