We will use the standard definition of the finite sets:
Inductive fin : nat -> Set :=
| F1 : forall {n : nat}, fin (S n)
| FS : forall {n : nat}, fin n -> fin (S n).
Let us assume that we have some P : forall {m : nat} (x y : fin m) : Set
(the
important thing is that both arguments of P
are of the same type). For
demonstration purposes, let P be just:
Definition P {m : nat} (x y : fin m) := {x = y} + {x <> y}.
Now, we would like to write a custom function that compares two numbers:
Fixpoint feq_dec {m : nat} (x y : fin m) : P x y.
The idea is straightforward: we match on x
and y
, for x = F1
, y = F1
we
trivially return equality, for x = FS x'
, y = FS y'
we recursively call
the procedure for x'
and y'
, for other cases we can trivially return
inequality.
A direct translation of this idea into Coq obviously fails:
refine (
match x, y return P x y with
| F1 _, F1 _ => _
| FS _ x', F1 _ => _
| F1 _, FS _ y' => _
| FS _ x', FS _ y' => _
end
).
(*
* The term "y0" has type "fin n0" while it is expected to have type "fin n".
*)
During the match on x
and y
we loose the type information so we cannot apply them to P
.
The standard trick with passing type equality proof does not help here:
refine (
match x in fin mx, y in fin my return mx = my -> P x y with
| F1 _, F1 _ => _
| FS _ x', F1 _ => _
| F1 _, FS _ y' => _
| FS _ x', FS _ y' => _
end eq_refl
).
(*
* The term "y0" has type "fin my" while it is expected to have type "fin mx".
*)
So, maybe we can use that proof of equality to cast x
have the same type as
y
?
Definition fcast {m1 m2 : nat} (Heq : m1 = m2) (x : fin m1) : fin m2.
Proof.
rewrite <- Heq.
apply x.
Defined.
We also need to be able to get rid of the cast later on. However, I noticed that
fcast eq_refl x = x
is not sufficient as we need to make it work with
arbitrary equivalence proof. I have found something called UIP that does exactly
what I need.
Require Import Coq.Program.Program.
Lemma fcast_void {m : nat} : forall (x : fin m) (H : m = m),
fcast H x = x.
Proof.
intros.
rewrite -> (UIP nat m m H eq_refl).
trivial.
Defined.
Now we are ready to finish the whole definition:
refine (
match x in fin mx, y in fin my
return forall (Hmx : m = mx) (Hmy : mx = my), P (fcast Hmy x) y with
| F1 _, F1 _ => fun Hmx Hmy => _
| FS _ x', F1 _ => fun Hmx Hmy => _
| F1 _, FS _ y' => fun Hmx Hmy => _
| FS _ x', FS _ y' => fun Hmx Hmy => _
end eq_refl eq_refl
); inversion Hmy; subst; rewrite fcast_void.
- left. reflexivity.
- right. intro Contra. inversion Contra.
- right. intro Contra. inversion Contra.
- destruct (feq_dec _ x' y') as [Heq | Hneq].
+ left. apply f_equal. apply Heq.
+ right. intro Contra. dependent destruction Contra. apply Hneq. reflexivity.
Defined.
It goes through! However, it doesn't evaluate to any useful value. For example
the following yields a term with five nested matches instead of a simple value
(in_right
or in_left
). I suspect the problem is with the UIP axiom that I
used.
Compute (@feq_dec 5 (FS F1) (FS F1)).
So in the end, the definition that I came up with is pretty much useless.
I have also tried doing nested matches using the convoy pattern instead of doing matching two values at
the same time but I hit the same obstacles: as soon as I do the matching on the second value, P
stops being applicable to it. Can I do it some other way?
You can write the terms by hand but it's a nightmare. Here I describe the computational part and use tactics to deal with the proving:
The function defined this way works as expected:
This code relies on 3 tricks:
1. Start by inspecting the bound
m
.Indeed if you don't know anything about the bound
m
, you'll learn two different facts from the match onx
andy
respectively and you'll need to reconcile these facts (i.e. show that the two predecessor form
you're given are in fact equal). If, on the other hand, you know thatm
has the shapeS m'
then you can...2. Use a
case
function inverting the term based on the bound's shapeIf you know that the bound has a shape
S m'
then you know for each one of yourfin
s that you are in one of two cases: either thefin
isF1
or it isFS x'
for somex'
.case
makes this formal:Coq will be smart enough to detect that the values we are returning from
case
are direct subterms of the arguments it takes. So performing recursive calls when bothx
andy
have the shapeFS _
won't be a problem!3. Use congruence with an ad-hoc function to peel off constructors
In the branch where we have performed a recursive call but got a negative answer in return, we need to prove
FS x' <> FS y'
knowing thatx' <> y'
. Which means that we need to turnHeq : FS x' = FS y'
intox' = y'
.Because
FS
has a complicated return type, simply performinginversion
onHeq
won't yield a usable result (we get an equality between dependent pairs of a natp
and afin p
). This is werefinpred
comes into play: it's a total function which, when faced withFS _
simply peels off theFS
constructor.Combined with
f_equal
andHeq
we get a proof thatSome x' = Some y'
on which we can useinversion
and get the equality we wanted.Edit: I've put all the code in a self-contained gist.