Pattern Matching

What is pattern matching?

Pattern matching is really nothing more than testing whether some expression or input matches a pattern that you specify.

ML uses pattern matching all over the place. For example, here's a really simple, trivial example, using constants:

case 1 of
  2 => "two"
| 1 => "one";
is pattern matching. Your input is the int 1, and you are trying to match it to the cases given. 1 doesn't match 2, but it matches 1, so the result of the entire expression is "one".

Note that when we try to compile this, we get the following

- case 1 of
= 2 => "two"
= | 1 => "one";
stdIn:17.1-19.13 Warning: match nonexhaustive
          2 => ...
          1 => ...
  
val it = "one" : string
The problem there was that, what if 1 (or whatever our input was) happened not to match any rule in our case expression? Say we had a 3 instead of a 1 What to do? Use the wildcard _. It will match anything you throw at it, that hasn't been matched already.
case 3 of
  2 => "two";
| 1 => "one";
| _ => "other";
Now, our expression will be of the value "other".

We can also use variables in our matching, for instance

val a = 3;
case a of
  2 => "two";
| 1 => "one";
| _ => "other";
does the exact same thing, except this time we're matching a variable a to the pattern rules.

Value Bindings

This previous example, was of course, pretty pointless since it doesn't really do anything. Here's an instance where pattern matching is a little more useful:

Value bindings in ML are essentially pattern matching as well. So when we write

val a = 3;
we are really matching 3 against our pattern a. At this point, we haven't specified anything about a, so anything can match to it, and that's why 3 matches to it. However, if we did
- val a:string = 3;
stdIn:42.1-42.17 Error: pattern and expression in val dec don't agree [literal]
  pattern:    string
  expression:    int
  in declaration:
    a : string = 3
Then we get that error, because the pattern of a, which is that a is a string because it has been ascribed to the string type, does not match the pattern of 3, because 3 is not a string.

Here's another example.

val (b::c) = [1,2,3];
This is also an instance of pattern matching. This time, we're trying to matching [1,2,3] onto the pattern b::c. The difference here is that now we're using variables b and c in our pattern. Since we're using the cons constructor, and trying to match onto an int list, b must be of type int, and c must be of type int list. So, our expression on the right [1,2,3] must be able to fit itself onto a pattern of int :: int list and it can! Hence, this pattern matches, and b is assigned to the int 3 and c is assigned to the int list [2,3].

Functions

We can also use pattern matching in functions.
- fun addNums (x,y) = x + y;
val addNums = fn : int * int -> int
- addNums (3,2);
val it = 5 : int
- 
This function takes a parameter in the pattern of a tuple of ints (int * int) and binds one to x and the other to y. When we run addNums(3,2), the tuple of ints (3,2) is matched againts (x,y), it matches, and 3 is bound to x and 2 is bound to y.

Say we want to match more than one pattern for the parameters of a function. We COULD do the following (this is example is in the lecture notes)

fun emptyTest aList =
    case aList of
      nil     => "empty!"
    | (x::xs) = "not empty; first elem: " ^ x;
But ML allows us to write an equivalent shorthand form of this as follows:
fun emptyTest nil     = "empty!"
    | emptyTest (x::xs) = "not empty; first elem: " ^ x;
All that's happening is, the parameter passed into the function is matched against nil, and then against x::xs. If it matches nil, it returns the value of the first rule, "empty!", otherwise if it matches x::xs (i.e. string :: string list, it will bind x to the first element of the list, xs to the tail of the list, and return the value "not empty; first elem: " concatenated with x.

Exercises

Keunwoo's supplementary exercises and solutions.