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" : stringThe 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.
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 = 3Then 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]
.
- 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.