Chapter 15 Extended pattern-matching
Cristina Cornes
This section describes the full form of pattern-matching in Coq terms.
15.1 Patterns
The full syntax of match is presented in figures 1.1
and 1.2. Identifiers in patterns are either
constructor names or variables. Any identifier that is not the
constructor of an inductive or coinductive type is considered to be a
variable. A variable name cannot occur more than once in a given
pattern. It is recommended to start variable names by a lowercase
letter.
If a pattern has the form (c x) where c is a constructor
symbol and x is a linear vector of variables, it is called
simple: it is the kind of pattern recognized by the basic
version of match. If a pattern is
not simple we call it nested.
A variable pattern matches any value, and the identifier is bound to
that value. The pattern ``_'' (called ``don't care'' or
``wildcard'' symbol) also matches any value, but does not bind anything. It
may occur an arbitrary number of times in a pattern. Alias patterns
written (pattern as identifier) are
also accepted. This pattern matches the same values as pattern
does and identifier is bound to the matched value. A list of
patterns separated with commas
is also considered as a pattern and is called multiple
pattern.
Since extended match expressions are compiled into the primitive
ones, the expressiveness of the theory remains the same. Once the
stage of parsing has finished only simple patterns remain. An easy way
to see the result of the expansion is by printing the term with
Print if the term is a constant, or using the command
Check.
The extended match still accepts an optional elimination
predicate given after the keyword return. Given a pattern
matching expression, if all the right hand sides of => (rhs in short) have the same type, then this type can be sometimes
synthesized, and so we can omit the return part. Otherwise
the predicate after return has to be provided, like for the basic
match.
Let us illustrate through examples the different aspects of extended
pattern matching. Consider for example the function that computes the
maximum of two natural numbers. We can write it in primitive syntax
by:
Coq < Fixpoint max (n m:nat) {struct m} : nat :=
Coq < match n with
Coq < | O => m
Coq < | S n' => match m with
Coq < | O => S n'
Coq < | S m' => S (max n' m')
Coq < end
Coq < end.
max is recursively defined
Using multiple patterns in the definition allows to write:
Coq < Reset max.
Coq < Fixpoint max (n m:nat) {struct m} : nat :=
Coq < match n, m with
Coq < | O, _ => m
Coq < | S n', O => S n'
Coq < | S n', S m' => S (max n' m')
Coq < end.
max is recursively defined
which will be compiled into the previous form.
The pattern-matching compilation strategy examines patterns from left
to right. A match expression is generated only when
there is at least one constructor in the column of patterns. E.g. the
following example does not build a match expression.
Coq < Check (fun x:nat => match x return nat with
Coq < | y => y
Coq < end).
fun x : nat => x
: nat -> nat
We can also use ``as patterns'' to associate a name to a
sub-pattern:
Coq < Reset max.
Coq < Fixpoint max (n m:nat) {struct n} : nat :=
Coq < match n, m with
Coq < | O, _ => m
Coq < | S n' as p, O => p
Coq < | S n', S m' => S (max n' m')
Coq < end.
max is recursively defined
Here is now an example of nested patterns:
Coq < Fixpoint even (n:nat) : bool :=
Coq < match n with
Coq < | O => true
Coq < | S O => false
Coq < | S (S n') => even n'
Coq < end.
even is recursively defined
This is compiled into:
Coq < Print even.
even =
(fix even (n : nat) : bool :=
match n with
| O => true
| S n0 => match n0 with
| O => false
| S n' => even n'
end
end)
: nat -> bool
Argument scope is [nat_scope]
In the previous examples patterns do not conflict with, but
sometimes it is comfortable to write patterns that admit a non
trivial superposition. Consider
the boolean function lef that given two natural numbers
yields true if the first one is less or equal than the second
one and false otherwise. We can write it as follows:
Coq < Fixpoint lef (n m:nat) {struct m} : bool :=
Coq < match n, m with
Coq < | O, x => true
Coq < | x, O => false
Coq < | S n, S m => lef n m
Coq < end.
lef is recursively defined
Note that the first and the second multiple pattern superpose because
the couple of values O O matches both. Thus, what is the result
of the function on those values? To eliminate ambiguity we use the
textual priority rule: we consider patterns ordered from top to
bottom, then a value is matched by the pattern at the ith row if and
only if it is not matched by some pattern of a previous row. Thus in the
example,
O O is matched by the first pattern, and so (lef O O)
yields true.
Another way to write this function is:
Coq < Reset lef.
Coq < Fixpoint lef (n m:nat) {struct m} : bool :=
Coq < match n, m with
Coq < | O, x => true
Coq < | S n, S m => lef n m
Coq < | _, _ => false
Coq < end.
lef is recursively defined
Here the last pattern superposes with the first two. Because
of the priority rule, the last pattern
will be used only for values that do not match neither the first nor
the second one.
Terms with useless patterns are not accepted by the
system. Here is an example:
Coq < Check (fun x:nat =>
Coq < match x with
Coq < | O => true
Coq < | S _ => false
Coq < | x => true
Coq < end).
Coq < Coq < Toplevel input, characters 246-255
> | x => true
> ^^^^^^^^^
Error: This clause is redundant
15.2 About patterns of parametric types
When matching objects of a parametric type, constructors in patterns
do not expect the parameter arguments. Their value is deduced
during expansion.
Consider for example the polymorphic lists:
Coq < Inductive List (A:Set) : Set :=
Coq < | nil : List A
Coq < | cons : A -> List A -> List A.
List is defined
List_rect is defined
List_ind is defined
List_rec is defined
We can check the function tail:
Coq < Check
Coq < (fun l:List nat =>
Coq < match l with
Coq < | nil => nil nat
Coq < | cons _ l' => l'
Coq < end).
fun l : List nat => match l with
| nil => nil nat
| cons _ l' => l'
end
: List nat -> List nat
When we use parameters in patterns there is an error message:
Coq < Check
Coq < (fun l:List nat =>
Coq < match l with
Coq < | nil A => nil nat
Coq < | cons A _ l' => l'
Coq < end).
Coq < Coq < Toplevel input, characters 220-231
> | cons A _ l' => l'
> ^^^^^^^^^^^
Error: The constructor cons expects 2 arguments.
15.3 Matching objects of dependent types
The previous examples illustrate pattern matching on objects of
non-dependent types, but we can also
use the expansion strategy to destructure objects of dependent type.
Consider the type listn of lists of a certain length:
Coq < Inductive listn : nat -> Set :=
Coq < | niln : listn 0
Coq < | consn : forall n:nat, nat -> listn n -> listn (S n).
listn is defined
listn_rect is defined
listn_ind is defined
listn_rec is defined
15.3.1 Understanding dependencies in patterns
We can define the function length over listn by:
Coq < Definition length (n:nat) (l:listn n) := n.
length is defined
Just for illustrating pattern matching,
we can define it by case analysis:
Coq < Reset length.
Coq < Definition length (n:nat) (l:listn n) :=
Coq < match l with
Coq < | niln => 0
Coq < | consn n _ _ => S n
Coq < end.
length is defined
We can understand the meaning of this definition using the
same notions of usual pattern matching.
15.3.2 When the elimination predicate must be provided
The examples given so far do not need an explicit elimination predicate
because all the rhs have the same type and the
strategy succeeds to synthesize it.
Unfortunately when dealing with dependent patterns it often happens
that we need to write cases where the type of the rhs are
different instances of the elimination predicate.
The function concat for listn
is an example where the branches have different type
and we need to provide the elimination predicate:
Coq < Fixpoint concat (n:nat) (l:listn n) (m:nat) (l':listn m) {struct l} :
Coq < listn (n + m) :=
Coq < match l in listn n return listn (n + m) with
Coq < | niln => l'
Coq < | consn n' a y => consn (n' + m) a (concat n' y m l')
Coq < end.
concat is recursively defined
The elimination predicate is fun (n:nat) (l:listn n) => listn (n+m).
In general if m has type (I q1... qr t1... ts) where
q1... qr are parameters, the elimination predicate should be of
the form :
fun y1...ys x:(I q1...qr y1...ys) => P.
In the concrete syntax, it should be written :
match m as x in (I _... _ y1... ys) return Q with ... end
The variables which appear in the in and as clause are new
and bounded in the property Q in the return clause. The
parameters of the inductive definitions should not be mentioned and
are replaced by _.
Recall that a list of patterns is also a pattern. So, when
we destructure several terms at the same time and the branches have
different type we need to provide
the elimination predicate for this multiple pattern.
It is done using the same scheme, each term may be associated to an
as and in clause in order to introduce a dependent product.
For example, an equivalent definition for concat (even though the matching on the second term is trivial) would have
been:
Coq < Reset concat.
Coq < Fixpoint concat (n:nat) (l:listn n) (m:nat) (l':listn m) {struct l} :
Coq < listn (n + m) :=
Coq < match l in listn n, l' return listn (n + m) with
Coq < | niln, x => x
Coq < | consn n' a y, x => consn (n' + m) a (concat n' y m x)
Coq < end.
concat is recursively defined
When the arity of the predicate (i.e. number of abstractions) is not
correct Coq raises an error message. For example:
Coq < Fixpoint concat
Coq < (n:nat) (l:listn n) (m:nat)
Coq < (l':listn m) {struct l} : listn (n + m) :=
Coq < match l, l' with
Coq < | niln, x => x
Coq < | consn n' a y, x => consn (n' + m) a (concat n' y m x)
Coq < end.
Coq < Coq < Coq < Toplevel input, characters 342-343
> | niln, x => x
> ^
Error:
In environment
concat : forall n : nat,
listn n -> forall m : nat, listn m -> listn (n + m)
n : nat
l : listn n
m : nat
l' : listn m
The term "l'" has type "listn m" while it is expected to have type
"listn (?31 + ?32)"
15.4 Using pattern matching to write proofs
In all the previous examples the elimination predicate does not depend
on the object(s) matched. But it may depend and the typical case
is when we write a proof by induction or a function that yields an
object of dependent type. An example of proof using match in
given in section 10.1
For example, we can write
the function buildlist that given a natural number
n builds a list of length n containing zeros as follows:
Coq < Fixpoint buildlist (n:nat) : listn n :=
Coq < match n return listn n with
Coq < | O => niln
Coq < | S n => consn n 0 (buildlist n)
Coq < end.
buildlist is recursively defined
We can also use multiple patterns.
Consider the following definition of the predicate less-equal
Le:
Coq < Inductive LE : nat -> nat -> Prop :=
Coq < | LEO : forall n:nat, LE 0 n
Coq < | LES : forall n m:nat, LE n m -> LE (S n) (S m).
LE is defined
LE_ind is defined
We can use multiple patterns to write the proof of the lemma
(n,m:nat) (LE n m)\/
(LE m n):
Coq < Fixpoint dec (n m:nat) {struct n} : LE n m \/ LE m n :=
Coq < match n, m return LE n m \/ LE m n with
Coq < | O, x => or_introl (LE x 0) (LEO x)
Coq < | x, O => or_intror (LE x 0) (LEO x)
Coq < | S n as n', S m as m' =>
Coq < match dec n m with
Coq < | or_introl h => or_introl (LE m' n') (LES n m h)
Coq < | or_intror h => or_intror (LE n' m') (LES m n h)
Coq < end
Coq < end.
dec is recursively defined
In the example of dec,
the first match is dependent while
the second is not.
The user can also use match in combination with the tactic
refine (see section 8.2.2) to build incomplete proofs
beginning with a match construction.
15.5 Pattern-matching on inductive objects involving local
definitions
If local definitions occur in the type of a constructor, then there
are two ways to match on this constructor. Either the local
definitions are skipped and matching is done only on the true arguments
of the constructors, or the bindings for local definitions can also
be caught in the matching.
Example.
Coq < Inductive list : nat -> Set :=
Coq < | nil : list 0
Coq < | cons : forall n:nat, let m := (2 * n) in list m -> list (S (S m)).
In the next example, the local definition is not caught.
Coq < Fixpoint length n (l:list n) {struct l} : nat :=
Coq < match l with
Coq < | nil => 0
Coq < | cons n l0 => S (length (2 * n) l0)
Coq < end.
length is recursively defined
But in this example, it is.
Coq < Fixpoint length' n (l:list n) {struct l} : nat :=
Coq < match l with
Coq < | nil => 0
Coq < | cons _ m l0 => S (length' m l0)
Coq < end.
length' is recursively defined
Remark: for a given matching clause, either none of the local
definitions or all of them can be caught.
15.6 Pattern-matching and coercions
If a mismatch occurs between the expected type of a pattern and its
actual type, a coercion made from constructors is sought. If such a
coercion can be found, it is automatically inserted around the
pattern.
Example:
Coq < Inductive I : Set :=
Coq < | C1 : nat -> I
Coq < | C2 : I -> I.
I is defined
I_rect is defined
I_ind is defined
I_rec is defined
Coq < Coercion C1 : nat >-> I.
C1 is now a coercion
Coq < Check (fun x => match x with
Coq < | C2 O => 0
Coq < | _ => 0
Coq < end).
fun x : I =>
match x with
| C1 _ => 0
| C2 i =>
match i with
| C1 n => match n with
| O => 0
| S _ => 0
end
| C2 _ => 0
end
end
: I -> nat
15.7 When does the expansion strategy fail ?
The strategy works very like in ML languages when treating
patterns of non-dependent type.
But there are new cases of failure that are due to the presence of
dependencies.
The error messages of the current implementation may be sometimes
confusing. When the tactic fails because patterns are somehow
incorrect then error messages refer to the initial expression. But the
strategy may succeed to build an expression whose sub-expressions are
well typed when the whole expression is not. In this situation the
message makes reference to the expanded expression. We encourage
users, when they have patterns with the same outer constructor in
different equations, to name the variable patterns in the same
positions with the same name.
E.g. to write (cons n O x) => e1
and (cons n _ x) => e2 instead of
(cons n O x) => e1 and
(cons n' _ x') => e2.
This helps to maintain certain name correspondence between the
generated expression and the original.
Here is a summary of the error messages corresponding to each situation:
Error messages: -
The constructor ident expects num arguments
The variable ident is bound several times
in pattern term
Found a constructor of inductive type term
while a constructor of term is expected
Patterns are incorrect (because constructors are not applied to
the correct number of the arguments, because they are not linear or
they are wrongly typed)
- Non exhaustive pattern-matching
the pattern matching is not exhaustive
- The elimination predicate term should be
of arity num (for non dependent case) or num (for dependent case)
The elimination predicate provided to match has not the
expected arity
- Unable to infer a match predicate
Either there is a type incompatiblity or the problem involves
dependencies
There is a type mismatch between the different branches
Then the user should provide an elimination predicate.