MPRI PRFA — Proof Assistants
Go back to the course homepage.
Inductive types — part 2
We continue after part 1, still looking at inductive types.
From Stdlib Require Import List. Import ListNotations. Set Default Goal Selector "!".
Dependent pairs
We define the type of dependent pairs, where the second component can depend on the first. We often call those Σ-types (hence the name sigT).
Arguments existT {A B}.
For instance, we can create a type of pairs, where the first component is always a boolean b, but the second is of type nat when b is true and of type list nat otherwise.
We can then create two elements of type T, a pair (true, 17) and a pair (false, [ 90 ; 4 ; 8 ]). This is something you could not do without dependent types (so for instance, not in OCaml).
Important, you should have a look at the induction principle for dependent pairs.
Remark. In the standard library, you have a notation for sigT: you can write { x : A & B x } for sigT B. This looks like the usual subset notation.
Similar to how a function of type A * B -> C is the same as A -> B -> C (which we call currying), we can turn a function of type sigT {A} B -> C to a (dependent) function of type forall (x : A), B x -> C.
Notice that forall can work for dependent functions and not just for propositions.
We can perform the opposite transformation.
And now we can prove that they are indeed inverse of each other.
A: Type
B: A -> Type
C: Type
f: sigT B -> C
p: sigT Bcar (cur f) p = f pA: Type
B: A -> Type
C: Type
f: sigT B -> C
p: sigT Bcar (cur f) p = f preflexivity.A: Type
B: A -> Type
C: Type
f: sigT B -> C
a: A
b: B acar (cur f) (existT a b) = f (existT a b)
The other direction is even simpler, there is nothing to destruct so the proof is direct by reflexivity.
A: Type
B: A -> Type
C: Type
f:= forall x: A, B x -> C
a: A
b: B acur (car f) a b = f a breflexivity.A: Type
B: A -> Type
C: Type
f:= forall x: A, B x -> C
a: A
b: B acur (car f) a b = f a b
Polymorphic iteration
We will now have a look at iter, a function such that iter f n x applies n times the function f, starting with the argument x. For instance iter f 2 x is f (f x).
We can show that iterating the successor n times is the same as adding n.
n, x: natn + x = iter S n xn, x: natn + x = iter S n xx: nat0 + x = iter S 0 xn, x: nat
IH: n + x = iter S n xS n + x = iter S (S n) xx: natx = xn, x: nat
IH: n + x = iter S n xS (n + x) = S (iter S n x)reflexivity.x: natx = xn, x: nat
IH: n + x = iter S n xS (n + x) = S (iter S n x)assumption.n, x: nat
IH: n + x = iter S n xn + x = iter S n x
Similarly, multiplication is iterated addition.
n, x: natn * x = iter (Nat.add x) n 0n, x: natn * x = iter (Nat.add x) n 0x: nat0 * x = iter (Nat.add x) 0 0n, x: nat
IH: n * x = iter (Nat.add x) n 0S n * x = iter (Nat.add x) (S n) 0x: nat0 = 0n, x: nat
IH: n * x = iter (Nat.add x) n 0x + n * x = x + iter (Nat.add x) n 0reflexivity.x: nat0 = 0n, x: nat
IH: n * x = iter (Nat.add x) n 0x + n * x = x + iter (Nat.add x) n 0assumption.n, x: nat
IH: n * x = iter (Nat.add x) n 0n * x = iter (Nat.add x) n 0
Indexed inductive types
We can define predicates using the Inductive keyword as well.
Being inductive, their proofs can be constructed using the constructor tactic.
Note: The Goal command is similar to Lemma except it doesn't require a name for a lemma which is useful when just checking things that are not useful later such as the fact the 4 is even…
even 4even 4even 2constructor.even 0
Case analysis works using inversion.
~ even 3~ even 3h: even 3Falseh: even 3
n: nat
H0: even 1
H: n = 1False
Here we learnt from even 3 that to build it we must have had even 1 first.
inversion H0.
But there is no rule (neither evenO nor evenS) that can unify with even 1 so there is no goal left, we are done!
inversion is useful but it is hard to predict its behaviour and to name the resulting hyoptheses. So we might want to use regular case analysis instead, ie. use destruct.
~ even 3~ even 3h: even 3FalseFalsen: nat
h: even nFalse
Here this quite terrible because destruct completely forgets about the fact we had 3 so we are left with an unprovable goal.
Abort.
There is a way to recover this information by telling Rocq to remember it as an equality using the remember tactic.
~ even 3~ even 3h: even 3Falsen: nat
e: n = 3
h: even nFalsee: 0 = 3Falsen: nat
e: S (S n) = 3
h: even nFalsee: 0 = 3False
See that n has been replaced by 0 but since we also kept the equality e we know this case is impossible.
discriminate.n: nat
e: S (S n) = 3
h: even nFalse
In the other branch, we have that 3 must be 2 + n for some even n which is also impossible but requires noticing that n must be 1. inversion on the equality will give us that.
n: nat
e: S (S n) = 3
h: even n
H0: n = 1False
We can then use subst to automatically substitute n by 1.
h: even 1
e: 3 = 3False
We can now rely on inversion again to discharge the goal.
inversion h.
Rather than an inductive predicate, we can also define a Boolean function that checks whether its argument is even.
We can show that the two are equivalent.
n: nateven n -> evenb n = truen: nateven n -> evenb n = truen: nat
h: even nevenb n = true
We can perform induction on the proof!
evenb 0 = truen: nat
h: even n
IHh: evenb n = trueevenb (S (S n)) = truereflexivity.evenb 0 = truen: nat
h: even n
IHh: evenb n = trueevenb (S (S n)) = trueassumption.n: nat
h: even n
IHh: evenb n = trueevenb n = true
And now the other direction.
n: natevenb n = true -> even nn: natevenb n = true -> even nn: nat
h: evenb n = trueeven n
This time we have to perform the induction on n.
h: evenb 0 = trueeven 0n: nat
h: evenb (S n) = true
ih: evenb n = true -> even neven (S n)constructor.h: evenb 0 = trueeven 0n: nat
h: evenb (S n) = true
ih: evenb n = true -> even neven (S n)h: evenb 1 = true
ih: evenb 0 = true -> even 0even 1n: nat
h: evenb (S (S n)) = true
ih: evenb (S n) = true -> even (S n)even (S (S n))h: evenb 1 = true
ih: evenb 0 = true -> even 0even 1discriminate.h: false = true
ih: evenb 0 = true -> even 0even 1n: nat
h: evenb (S (S n)) = true
ih: evenb (S n) = true -> even (S n)even (S (S n))n: nat
h: evenb (S (S n)) = true
ih: evenb (S n) = true -> even (S n)even n
Note how we are stuck here: The inductive hypothesis talks about S n instead of n. But this we cannot generalise.
Abort.
The solution is to use a stronger form of induction that lets you conclude about any smaller argument. We'll require Lia which provides the lia tactic (for linear integer arithmetic) to help us deal with some arithmetic goals.
From Stdlib Require Import Lia.
In the induction principle below we use forall which is used to quantify over anything from natural numbers to propositions. We can use intros to introduce those variables, the same way we do it for implication.
The strong induction principle is as you'd expect: to prove some property P about n, you can assume it holds for all natural numbers that are smaller.
forall P : nat -> Prop, (forall n : nat, (forall m : nat, m < n -> P m) -> P n) -> forall n : nat, P nforall P : nat -> Prop, (forall n : nat, (forall m : nat, m < n -> P m) -> P n) -> forall n : nat, P nP: nat -> Prop
h:= forall n : nat, (forall m: nat, m < n -> P m) -> P n
n: natP n
We'll show a stronger property by induction, that P is valid for n and everything below.
P: nat -> Prop
h:= forall n : nat, (forall m: nat, m < n -> P m) -> P n
n: natforall m : nat, m <= n -> P mP: nat -> Prop
h:= forall n : nat, (forall m: nat, m < n -> P m) -> P n
n: nat
hn:= forall m: nat, m <= n -> P mP nP: nat -> Prop
h:= forall n : nat, (forall m: nat, m < n -> P m) -> P n
n: natforall m : nat, m <= n -> P mP: nat -> Prop
h:= forall n : nat, (forall m: nat, m < n -> P m) -> P nforall m : nat, m <= 0 -> P mP: nat -> Prop
h:= forall n : nat, (forall m: nat, m < n -> P m) -> P n
n: nat
ih:= forall m: nat, m <= n -> P mforall m : nat, m <= S n -> P mP: nat -> Prop
h:= forall n : nat, (forall m: nat, m < n -> P m) -> P nforall m : nat, m <= 0 -> P mP: nat -> Prop
h:= forall n : nat, (forall m: nat, m < n -> P m) -> P n
m: nat
hm: m <= 0P mP: nat -> Prop
h:= forall n : nat, (forall m: nat, m < n -> P m) -> P n
m: nat
hm: m <= 0
H: m = 0P 0P: nat -> Prop
h:= forall n : nat, (forall m: nat, m < n -> P m) -> P n
m: nat
hm: m <= 0
H: m = 0forall m0 : nat, m0 < 0 -> P m0lia.P: nat -> Prop
h:= forall n : nat, (forall m: nat, m < n -> P m) -> P n
m: nat
hm: m <= 0
H: m = 0
k: nat
hk: k < 0P k
lia knows there can't be k < 0 so it solves the goal for us. It is implictly using a proof of ~ k < 0 together with False elimination.
P: nat -> Prop
h:= forall n : nat, (forall m: nat, m < n -> P m) -> P n
n: nat
ih:= forall m: nat, m <= n -> P mforall m : nat, m <= S n -> P mP: nat -> Prop
h:= forall n : nat, (forall m: nat, m < n -> P m) -> P n
n: nat
ih:= forall m: nat, m <= n -> P m
m: nat
hm: m <= S nP mP: nat -> Prop
h:= forall n : nat, (forall m: nat, m < n -> P m) -> P n
n: nat
ih:= forall m: nat, m <= n -> P m
m: nat
hm: m <= S n
H: m = S nP (S n)P: nat -> Prop
h:= forall n : nat, (forall m: nat, m < n -> P m) -> P n
n: nat
ih:= forall m: nat, m <= n -> P m
m: nat
hm: m <= S n
m0: nat
H0: m <= n
H: m0 = nP mP: nat -> Prop
h:= forall n : nat, (forall m: nat, m < n -> P m) -> P n
n: nat
ih:= forall m: nat, m <= n -> P m
m: nat
hm: m <= S n
H: m = S nP (S n)P: nat -> Prop
h:= forall n : nat, (forall m: nat, m < n -> P m) -> P n
n: nat
ih:= forall m: nat, m <= n -> P m
m: nat
hm: m <= S n
H: m = S nforall m0 : nat, m0 < S n -> P m0P: nat -> Prop
h:= forall n : nat, (forall m: nat, m < n -> P m) -> P n
n: nat
ih:= forall m: nat, m <= n -> P m
m: nat
hm: m <= S n
H: m = S n
k: nat
hk: k < S nP klia.P: nat -> Prop
h:= forall n : nat, (forall m: nat, m < n -> P m) -> P n
n: nat
ih:= forall m: nat, m <= n -> P m
m: nat
hm: m <= S n
H: m = S n
k: nat
hk: k < S nk <= nP: nat -> Prop
h:= forall n : nat, (forall m: nat, m < n -> P m) -> P n
n: nat
ih:= forall m: nat, m <= n -> P m
m: nat
hm: m <= S n
m0: nat
H0: m <= n
H: m0 = nP mlia.P: nat -> Prop
h:= forall n : nat, (forall m: nat, m < n -> P m) -> P n
n: nat
ih:= forall m: nat, m <= n -> P m
m: nat
hm: m <= S n
m0: nat
H0: m <= n
H: m0 = nm <= nP: nat -> Prop
h:= forall n : nat, (forall m: nat, m < n -> P m) -> P n
n: nat
hn:= forall m: nat, m <= n -> P mP nlia.P: nat -> Prop
h:= forall n : nat, (forall m: nat, m < n -> P m) -> P n
n: nat
hn:= forall m: nat, m <= n -> P mn <= n
Back to even. We can now use our stronger induction principle thanks to the using clause of the induction tactic. Indeed, the tactic can use anything that looks like an induction principle (which is admittedly a vague notion) which can be very handy!
n: natevenb n = true -> even nn: natevenb n = true -> even nn: nat
h: evenb n = trueeven nn: nat
ih:= forall m: nat, m < n -> evenb m = true -> even m
h: evenb n = trueeven n
As opposed to regular induction, we only have one case to consider: an arbitrary
n but for which we have an induction hypothesis.
We still need to perform case analysis, so we'll do it manually.
We have to consider the cases 0, 1 and S (S n).
We could perform destruct n once to get cases 0
and S n
and then in
the second branch do destruct again, but we can do it all at once by using
square brackets within square brackets.
ih:= forall m: nat, m < 0 -> evenb m = true -> even m
h: evenb 0 = trueeven 0ih:= forall m: nat, m < 1 -> evenb m = true -> even m
h: evenb 1 = trueeven 1n: nat
ih:= forall m: nat, m < S (S n) -> evenb m = true -> even m
h: evenb (S (S n)) = trueeven (S (S n))constructor.ih:= forall m: nat, m < 0 -> evenb m = true -> even m
h: evenb 0 = trueeven 0ih:= forall m: nat, m < 1 -> evenb m = true -> even m
h: evenb 1 = trueeven 1discriminate.ih:= forall m: nat, m < 1 -> evenb m = true -> even m
h: false = trueeven 1n: nat
ih:= forall m: nat, m < S (S n) -> evenb m = true -> even m
h: evenb (S (S n)) = trueeven (S (S n))n: nat
ih:= forall m: nat, m < S (S n) -> evenb m = true -> even m
h: evenb n = trueeven (S (S n))n: nat
ih:= forall m: nat, m < S (S n) -> evenb m = true -> even m
h: evenb n = trueeven nn: nat
ih:= forall m: nat, m < S (S n) -> evenb m = true -> even m
h: evenb n = truen < S (S n)n: nat
ih:= forall m: nat, m < S (S n) -> evenb m = true -> even m
h: evenb n = trueevenb n = truelia.n: nat
ih:= forall m: nat, m < S (S n) -> evenb m = true -> even m
h: evenb n = truen < S (S n)assumption.n: nat
ih:= forall m: nat, m < S (S n) -> evenb m = true -> even m
h: evenb n = trueevenb n = true
The "less than" predicate <= we have used above is defined as follows.
The idea is that n ≤ n always holds, and if n ≤ m then n ≤ S m. In other words, if you can remove successors S from m and reach n then n ≤ m.
We build the proof as hinted above, by removing S from 5 until we reach 3 which takes only two steps.
le 3 5le 3 5le 3 4constructor.le 3 3
To show that 1 is not below 0 we can use inversion again.
~ le 1 0~ le 1 0inversion h.h: le 1 0False
As for even we can also do it more manually.
~ le 1 0~ le 1 0h: le 1 0Falsen1: nat
e1: n1 = 1
h: le n1 0Falsen1, n0: nat
e0: n0 = 0
e1: n1 = S n0
h: le n1 n0Falsen: nat
e0: n = 0
e1: n = S nFalsem: nat
e0: S m = 0
n: nat
e1: n = S (S m)
h: le n mFalsen: nat
e0: n = 0
e1: n = S nFalsediscriminate.e1: 0 = 1Falsem: nat
e0: S m = 0
n: nat
e1: n = S (S m)
h: le n mFalsediscriminate.m: nat
e0: S m = 0
h: le (S (S m)) mFalse
As a sanity check, we can verify the following property.
Below exists (x : A), P x is very similar to sigT P except that P x is a proposition. We can prove exists x, P x by e.g., using exists 0 and then proving P 0.
n, m: natle n m <-> (exists k : nat, k + n = m)n, m: natle n m <-> (exists k : nat, k + n = m)n, m: natle n m -> exists k : nat, k + n = mn, m: nat(exists k : nat, k + n = m) -> le n mn, m: natle n m -> exists k : nat, k + n = mn, m: nat
h: le n mexists k : nat, k + n = mn: natexists k : nat, k + n = nn, m: nat
h: le n m
ih:= exists k: nat, k + n = mexists k : nat, k + n = S mn: natexists k : nat, k + n = nreflexivity.n: nat0 + n = nn, m: nat
h: le n m
ih:= exists k: nat, k + n = mexists k : nat, k + n = S mn, m: nat
h: le n m
k: nat
ih: k + n = mexists k0 : nat, k0 + n = S mn, k: nat
h: le n (k + n)exists k0 : nat, k0 + n = S (k + n)n, k: nat
h: le n (k + n)S k + n = S (k + n)reflexivity.n, k: nat
h: le n (k + n)S (k + n) = S (k + n)n, m: nat(exists k : nat, k + n = m) -> le n m
Instead of doing intro x and then destruct x as [a b] we can do intros [a b].
n, m, k: nat
h: k + n = mle n mn, k: natle n (k + n)n: natle n (0 + n)n, k: nat
ih: le n (k + n)le n (S k + n)n: natle n (0 + n)constructor.n: natle n nn, k: nat
ih: le n (k + n)le n (S k + n)n, k: nat
ih: le n (k + n)le n (S (k + n))assumption.n, k: nat
ih: le n (k + n)le n (k + n)
We can also show transitivity.
n, m, k: natle n m -> le m k -> le n kn, m, k: natle n m -> le m k -> le n kn, m, k: nat
hnm: le n m
hmk: le m kle n kn, m: nat
hnm: le n mle n mn, m: nat
hnm: le n m
k: nat
hmk: le m k
ih: le n m -> le n kle n (S k)assumption.n, m: nat
hnm: le n mle n mn, m: nat
hnm: le n m
k: nat
hmk: le m k
ih: le n m -> le n kle n (S k)n, m: nat
hnm: le n m
k: nat
hmk: le m k
ih: le n m -> le n kle n kassumption.n, m: nat
hnm: le n m
k: nat
hmk: le m k
ih: le n m -> le n kle n m
We can also define the notion of reflexive transitive closure of a relation R : A -> A -> Prop on A like so.
Arguments trans {A R}.
Then le is just the closure of being the successor.
n, m: natle n m <-> rtclos (fun n0 m0 : nat => m0 = S n0) n mn, m: natle n m <-> rtclos (fun n0 m0 : nat => m0 = S n0) n mn, m: natle n m -> rtclos (fun n0 m0 : nat => m0 = S n0) n mn, m: natrtclos (fun n0 m0 : nat => m0 = S n0) n m -> le n mn, m: natle n m -> rtclos (fun n0 m0 : nat => m0 = S n0) n mn, m: nat
h: le n mrtclos (fun n0 m0 : nat => m0 = S n0) n mn: natrtclos (fun n0 m : nat => m = S n0) n nn, m: nat
h: le n m
ih:= rtclos (fun n m: nat => m = S n) n mrtclos (fun n0 m0 : nat => m0 = S n0) n (S m)constructor.n: natrtclos (fun n0 m : nat => m = S n0) n nn, m: nat
h: le n m
ih:= rtclos (fun n m: nat => m = S n) n mrtclos (fun n0 m0 : nat => m0 = S n0) n (S m)
If we just apply constructor then it's going to pick incl which is not what we want. What we want is to use trans:
n, m: nat
h: le n m
ih:= rtclos (fun n m: nat => m = S n) n mrtclos (fun n0 m0 : nat => m0 = S n0) n mn, m: nat
h: le n m
ih:= rtclos (fun n m: nat => m = S n) n mrtclos (fun n0 m0 : nat => m0 = S n0) m (S m)n, m: nat
h: le n m
ih:= rtclos (fun n m: nat => m = S n) n mrtclos (fun n0 m0 : nat => m0 = S n0) m (S m)
We just applied assumption to the first goal this way.
It can be nicer than having many levels of nested bullets.
reflexivity.n, m: nat
h: le n m
ih:= rtclos (fun n m: nat => m = S n) n mS m = S mn, m: natrtclos (fun n0 m0 : nat => m0 = S n0) n m -> le n mn, m: nat
h:= rtclos (fun n m: nat => m = S n) n mle n mn: natle n nn, m: nat
h: m = S nle n mn, m, k: nat
hnm:= rtclos (fun n m: nat => m = S n) n m
hmk:= rtclos (fun n m: nat => m = S n) m k
ihnm: le n m
ihmk: le m kle n kconstructor.n: natle n nn, m: nat
h: m = S nle n mn: natle n (S n)constructor.n: natle n nn, m, k: nat
hnm:= rtclos (fun n m: nat => m = S n) n m
hmk:= rtclos (fun n m: nat => m = S n) m k
ihnm: le n m
ihmk: le m kle n kall: assumption.n, m, k: nat
hnm:= rtclos (fun n m: nat => m = S n) n m
hmk:= rtclos (fun n m: nat => m = S n) m k
ihnm: le n m
ihmk: le m kle n mn, m, k: nat
hnm:= rtclos (fun n m: nat => m = S n) n m
hmk:= rtclos (fun n m: nat => m = S n) m k
ihnm: le n m
ihmk: le m kle m k