MPRI PRFA — Proof Assistants
🏠 Go back to the course homepage.
Advanced elimination / induction
- The goal of this lecture is to:
Understand more about sorts in Rocq, i.e. about Type and Prop. This is helpful to decipher certain error messages that occasionally occur in practical developments.
Understand how induction principles are implemented and use this knowledge to implement induction principles for inductive types where Rocq's automatic generation is too weak.
Let us start with sorts.
From Stdlib Require Import Arith Lia List. Import ListNotations.
Sorts
We have already seen Type and Prop. They are also called universes or sorts. You have seen that seemingly Type : Type:
But also that Type is actually a hierarchy of universes Type@{i} in order to avoid inconsistencies with Type : Type, which becomes visible once universe printing is enabled.
Set Printing Universes.
Below we will see more about the typing rules of Type, in particular a reminder how the typing rule of forall works.
We use the section mechanism which allows us to declare universe levels and so on.
Section Universes.Command Universes declares universe levels, while Constraint lets us add (in)equalities between them. Below we declare levels i and j together with constraint i < j. We can see that Type@{i} is of type Type@{j} but not the converse.
Universes i j. Constraint i < j.
Universe of a ∀ quantifier
Let us now have a look at the type of a forall in Rocq. For this we're going to use another feature of the section mechanism: the Context command allows us to add assumptions in our context, similar to assumptions for a lemma.
Universe k.
As you can see below, the type of a forall is the maximum of the types of the domain and codomain. See what it means for a forall containing Type.
Cumulativity
By the way, above, we've just seen a use of so-called cumulativity which says that Type@{i} lives in Type@{j} for any j that's bigger than i. By contrast, in Agda or Lean, we have uniqueness of the type and thus Type@{i} is of Type@{i+1} and that's it.
Cumulativity is actually more than that. It is in fact a form of subtyping that says that Type@{i} is a subtype of Type@{j} whenever i ≤ j.
See how A : Type@{i} also lives in Type@{j} but how the converse wouldn't work.
Prop lives in Type@{i} for all i.
Here you see Rocq mentions a constraint that Set < i. There is indeed a third kind of universe which we did not mention but you may have seen already. It's called Set. You can think of it as the lowest Type universe: Type@{0}, but in Rocq it's actually Type@{Set} because the universe levels (i, j, k etc.) are not natural numbers but abstract variables with constraints like the one we added above.
Set is the smallest Type and it is the only one which does not contain Prop. We do have cumulativity going from Prop to Set and Type however.
An overview:
Prop : Type@{Set+1}
Set : Type@{Set+1}
Prop <= Type@{Set+1}
Set <= Type@{Set+1}
Type@{i} : Type@{i+1}
Type@{i} <= Type@{i+1}
See how it goes in practice:
Impredicativity
Prop has other particularities. When considering a forall that ends in a proposition, then the whole forall stays a proposition. This means that things like forall (n : nat), n = n or forall (P : Prop), P are propositions. This property is called impredicativity because things like forall (P : Prop), P are actually quantifying over themselves (P can be instantiated with forall (P : Prop), P).
This is another big difference with Agda which is predicative instead. To remedy this they have to also add universe levels to Prop like Prop@{i}. Lean on the other hands does things the same way as Rocq. In fact, impredicativity is very strong and necessary to achieve the full power of ZFC.
In Rocq, the universe of a forall ending in a proposition is always a proposition, regardless of the size of what we quantify over. This comes from the intuition (and models) that Prop is proof-irrelevant: all propositions are morally either empty (False) or contain only one element (True) so Prop can be seen (modelled) as a set with two elements so it is always small.
The commands below illustrate all we just said.
Unset Printing Universes. End Universes.
Elimination restrictions
Universes are also important when defining inductive types. You may wonder when to put your type in Prop or in Type. We have seen several examples: things like disjunction and conjunction are placed in Prop, but bool is a Type for instance.
The central reasons are the Prop is an impredicative sort (more on that below), but also that Prop can be axiomatically assumed to be proof irrelevant. Proof irrelevance is expressed as follows.
It states that any two proofs of a given proposition are always equal. In other words, PI being consistent means that proofs cannot impact computation in a meaningful way. That is, we cannot analyse a proof to build a a function that is supposed to compute a Boolean.
Consider a variant of bool in Prop below.
As long as we remain in Prop, we can do pretty much the same as before.
forall b : prop_bool, pneg (pneg b) = bforall b : prop_bool, pneg (pneg b) = ball: reflexivity.pneg (pneg ptrue) = ptruepneg (pneg pfalse) = pfalse
We can also map the usual Booleans into them.
But, crucially, we can't do the converse!
Rocq complains that proofs can only be eliminated to build other proofs. The same happens in proof mode of course.
prop_bool -> boolprop_bool -> boolb: prop_boolboolAbort.b: prop_boolbool
We can instead prove something weaker like this:
forall p : prop_bool, exists b : bool, to_prop_bool b = pforall p : prop_bool, exists b : bool, to_prop_bool b = pexists b : bool, to_prop_bool b = ptrueexists b : bool, to_prop_bool b = pfalseexists b : bool, to_prop_bool b = ptruereflexivity.to_prop_bool true = ptrueexists b : bool, to_prop_bool b = pfalsereflexivity.to_prop_bool false = pfalse
We can however not prove that there is a function inverting to_prop_bool because this would be inconsistent with PI:
f: prop_bool -> bool(forall b : bool, f (to_prop_bool b) = b) -> ~ PIf: prop_bool -> bool(forall b : bool, f (to_prop_bool b) = b) -> ~ PIf: prop_bool -> bool
H:= forall b: bool, f (to_prop_bool b) = b
pi: PIFalsef: prop_bool -> bool
H:= forall b: bool, f (to_prop_bool b) = b
pi: ptrue = pfalseFalsef: prop_bool -> bool
H:= forall b: bool, f (to_prop_bool b) = b
pi: ptrue = pfalse
e1: f (to_prop_bool true) = trueFalsef: prop_bool -> bool
H:= forall b: bool, f (to_prop_bool b) = b
pi: ptrue = pfalse
e1: f (to_prop_bool true) = true
e2: f (to_prop_bool false) = falseFalsef: prop_bool -> bool
H:= forall b: bool, f (to_prop_bool b) = b
pi: ptrue = pfalse
e1: f ptrue = true
e2: f pfalse = falseFalsecongruence.f: prop_bool -> bool
H:= forall b: bool, f (to_prop_bool b) = b
pi: ptrue = pfalse
e1: f pfalse = true
e2: f pfalse = falseFalse
Subsingleton criterion
However, there are crucial exceptions to the rule: If a type has at most one constructor and all the non-parameter arguments of the constructor are proofs, then such eliminations are allowed.
This property is called (sub)singleton criterion because it essentially says that you can inspect (as in case analysis) a proof, as long as there can be at most one such proof. So a term that depends on it, doesn't really depend on it in the end, but merely on the fact that there exists a proof.
This is thanks to this property that we can eliminate a proof of False to produce anything else.
False -> natFalse -> natdestruct bot.bot: Falsenat
It also works with True, but it's not so interesting. Another more interesting case is equality. This means, you can use tactics such as rewrite on any type.
forall (n m : nat) (P : nat -> Type), P (n + m) -> P (m + n)forall (n m : nat) (P : nat -> Type), P (n + m) -> P (m + n)n, m: nat
P: nat -> Type
x: P (n + m)P (m + n)assumption.n, m: nat
P: nat -> Type
x: P (n + m)P (n + m)
The above we can do in general:
forall (A : Type) (P : A -> Type) (u v : A), u = v -> P u -> P vforall (A : Type) (P : A -> Type) (u v : A), u = v -> P u -> P vnow rewrite e.A: Type
P: A -> Type
u, v: A
e: u = vP u -> P v
Disjunction for instance does not fall under the criterion:
forall P Q : Prop, P \/ Q -> boolforall P Q : Prop, P \/ Q -> boolP, Q: Prop
h: P \/ QboolAbort.P, Q: Prop
h: P \/ Qbool
We could try to cheat by boxing a proposition so it only has one constructor but it doesn't actually help.
forall P Q : Prop, Box (P \/ Q) -> boolforall P Q : Prop, Box (P \/ Q) -> boolP, Q: Prop
h: Box (P \/ Q)boolP, Q: Prop
p: P \/ QboolAbort.P, Q: Prop
p: P \/ Qbool
Induction principles
Let us now move to induction principles. For this, we'll start with trees.
We can easily define binary trees (with labels in A) as follows.
Arguments bt_leaf {A}. Arguments bt_node {A}.
As for every inductive type, Rocq generates induction principles corresponding to the various sorts:
bintree_ind for Prop
bintree_rect for Type
bintree_rec for Set (but you can ignore it and use bintree_rect)
They only vary in the return type of the motive (the return predicate).
Now consider the following map function on bintree.
When proving properties about it, we will use the induction tactic which picks up the corresponding induction principle, below bintree_ind and apply it for us.
forall (A : Type) (t : bintree A), bt_map (fun x : A => x) t = tforall (A : Type) (t : bintree A), bt_map (fun x : A => x) t = tA: Type
t: bintree Abt_map (fun x : A => x) t = tA: Type
a: Abt_map (fun x : A => x) (bt_leaf a) = bt_leaf aA: Type
a: A
l, r: bintree A
ihl:= bt_map (fun x: A => x) l = l
ihr:= bt_map (fun x: A => x) r = rbt_map (fun x : A => x) (bt_node a l r) = bt_node a l rreflexivity.A: Type
a: Abt_map (fun x : A => x) (bt_leaf a) = bt_leaf aA: Type
a: A
l, r: bintree A
ihl:= bt_map (fun x: A => x) l = l
ihr:= bt_map (fun x: A => x) r = rbt_map (fun x : A => x) (bt_node a l r) = bt_node a l rA: Type
a: A
l, r: bintree A
ihl:= bt_map (fun x: A => x) l = l
ihr:= bt_map (fun x: A => x) r = rbt_node a (bt_map (fun x : A => x) l) (bt_map (fun x : A => x) r) = bt_node a l rreflexivity.A: Type
a: A
l, r: bintree A
ihl:= bt_map (fun x: A => x) l = l
ihr:= bt_map (fun x: A => x) r = rbt_node a l r = bt_node a l r
If you print the term, you will see that it indeed starts with an application of bintree_ind. No need to read the rest for now.
General trees.
Now say we don't want to limit ourselves to binary trees. The obvious way to define trees is to have nodes take a list of subtrees. Now as we're going to see, the induction principle that Rocq generates is insufficient (although there is hope that the situation improves in the future).
Arguments node {A}.
Now cpnsider a map from bintree to tree, as well as a map function for tree.
We can prove the following property by induction on bintree.
forall (A B : Type) (f : A -> B) (t : bintree A), tree_map f (bt_to_tree t) = bt_to_tree (bt_map f t)forall (A B : Type) (f : A -> B) (t : bintree A), tree_map f (bt_to_tree t) = bt_to_tree (bt_map f t)A, B: Type
f: A -> B
t: bintree Atree_map f (bt_to_tree t) = bt_to_tree (bt_map f t)A, B: Type
f: A -> B
a: Atree_map f (bt_to_tree (bt_leaf a)) = bt_to_tree (bt_map f (bt_leaf a))A, B: Type
f: A -> B
a: A
l, r: bintree A
ihl: tree_map f (bt_to_tree l) = bt_to_tree (bt_map f l)
ihr: tree_map f (bt_to_tree r) = bt_to_tree (bt_map f r)tree_map f (bt_to_tree (bt_node a l r)) = bt_to_tree (bt_map f (bt_node a l r))reflexivity.A, B: Type
f: A -> B
a: Atree_map f (bt_to_tree (bt_leaf a)) = bt_to_tree (bt_map f (bt_leaf a))A, B: Type
f: A -> B
a: A
l, r: bintree A
ihl: tree_map f (bt_to_tree l) = bt_to_tree (bt_map f l)
ihr: tree_map f (bt_to_tree r) = bt_to_tree (bt_map f r)tree_map f (bt_to_tree (bt_node a l r)) = bt_to_tree (bt_map f (bt_node a l r))A, B: Type
f: A -> B
a: A
l, r: bintree A
ihl: tree_map f (bt_to_tree l) = bt_to_tree (bt_map f l)
ihr: tree_map f (bt_to_tree r) = bt_to_tree (bt_map f r)node (f a) [tree_map f (bt_to_tree l); tree_map f (bt_to_tree r)] = node (f a) [bt_to_tree (bt_map f l); bt_to_tree (bt_map f r)]reflexivity.A, B: Type
f: A -> B
a: A
l, r: bintree A
ihl: tree_map f (bt_to_tree l) = bt_to_tree (bt_map f l)
ihr: tree_map f (bt_to_tree r) = bt_to_tree (bt_map f r)node (f a) [bt_to_tree (bt_map f l); bt_to_tree (bt_map f r)] = node (f a) [bt_to_tree (bt_map f l); bt_to_tree (bt_map f r)]
However, for the following lemma, we'll see induction fails us.
forall (A : Type) (t : tree A), tree_map (fun x : A => x) t = tforall (A : Type) (t : tree A), tree_map (fun x : A => x) t = tA: Type
t: tree Atree_map (fun x : A => x) t = tA: Type
a: A
children: list (tree A)tree_map (fun x : A => x) (node a children) = node a childrenA: Type
a: A
children: list (tree A)node a (map (tree_map (fun x : A => x)) children) = node a childrenA: Type
a: A
children: list (tree A)map (tree_map (fun x : A => x)) children = children
We have no induction hypopthesis at all!!
Abort.Indeed, see what Rocq generated for us:
It essentially corresponds to a case analysis, but no induction hypotesis is avaiable. This is because we are using what we call nested inductive types (because in the definition of tree, tree appears again below—or nested in—the list type former). Rocq does not know what to do with assumptions of type list so it just does nothing.
Of course, this is silly and we probably still have to suffer it because of backwards compatibility. Let's see how we can do better.
First, notice that Rocq still supports this kind of induction by using fixpoints. We can actually use them interactively.
tree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
t: tree Atree_map (fun x : A => x) t = ttree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
t: tree Atree_map (fun x : A => x) t = ttree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
children: list (tree A)tree_map (fun x : A => x) (node a children) = node a childrentree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
children: list (tree A)node a (map (tree_map (fun x : A => x)) children) = node a childrentree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
children: list (tree A)map (tree_map (fun x : A => x)) children = childrentree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
children: list (tree A)map ?g children = childrentree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
children: list (tree A)forall a0 : tree A, tree_map (fun x : A => x) a0 = ?g a0apply map_id.tree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
children: list (tree A)map ?g children = childrentree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
children: list (tree A)forall a0 : tree A, tree_map (fun x : A => x) a0 = (fun x : tree A => x) a0tree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
children: list (tree A)forall a0 : tree A, tree_map (fun x : A => x) a0 = a0apply tree_map_id.tree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
children: list (tree A)
x: tree Atree_map (fun x0 : A => x0) x = xAbort.
Qed fails here because Rocq cannot see our fixpoint terminates. Indeed, we used a recursive call on something completely unrelated. We are only told now because Rocq only checks for termination at Qed time, and not before. There is a way to know beforehand though: the Guarded command checks that the guard condition (which verifies termination) holds up to the point where it's called.
tree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
t: tree Atree_map (fun x : A => x) t = ttree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
t: tree Atree_map (fun x : A => x) t = ttree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
children: list (tree A)tree_map (fun x : A => x) (node a children) = node a childrentree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
children: list (tree A)node a (map (tree_map (fun x : A => x)) children) = node a childrentree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
children: list (tree A)map (tree_map (fun x : A => x)) children = childrentree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
children: list (tree A)map (tree_map (fun x : A => x)) children = childrentree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
children: list (tree A)map ?g children = childrentree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
children: list (tree A)forall a0 : tree A, In a0 children -> tree_map (fun x : A => x) a0 = ?g a0apply map_id.tree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
children: list (tree A)map ?g children = childrentree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
children: list (tree A)forall a0 : tree A, In a0 children -> tree_map (fun x : A => x) a0 = (fun x : tree A => x) a0tree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
children: list (tree A)forall a0 : tree A, In a0 children -> tree_map (fun x : A => x) a0 = (fun x : tree A => x) a0tree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
children: list (tree A)forall a0 : tree A, In a0 children -> tree_map (fun x : A => x) a0 = a0tree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
children: list (tree A)
x: tree A
h: In x childrentree_map (fun x0 : A => x0) x = xapply tree_map_id.tree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
children: list (tree A)
x: tree A
h: In x childrentree_map (fun x0 : A => x0) x = xAbort.
This time it should actually work because morally we only use recursive calls on elements inside the list, but Rocq doesn't know it.
We in fact need to be able to recurse on the list, so we the same way we nested inductive types, we need to nest fixpoints. For this we can use the fix tactic which introduces a fixed point. We need to revert the argument on which we want to recurse (to put it back into the goal) and then fix aux 1 will introduce a new hypothesis named aux corresponding to potential recursive calls.
tree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
t: tree Atree_map (fun x : A => x) t = ttree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
t: tree Atree_map (fun x : A => x) t = ttree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
children: list (tree A)tree_map (fun x : A => x) (node a children) = node a childrentree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
children: list (tree A)node a (map (tree_map (fun x : A => x)) children) = node a childrentree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
children: list (tree A)map (tree_map (fun x : A => x)) children = childrentree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: Aforall children : list (tree A), map (tree_map (fun x : A => x)) children = childrentree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
aux:= forall children : list (tree A), map (tree_map (fun x: A => x)) children = childrenforall children : list (tree A), map (tree_map (fun x : A => x)) children = childrentree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
aux:= forall children : list (tree A), map (tree_map (fun x: A => x)) children = children
l: list (tree A)map (tree_map (fun x : A => x)) l = ltree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
aux:= forall children : list (tree A), map (tree_map (fun x: A => x)) children = childrenmap (tree_map (fun x : A => x)) [] = []tree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
aux:= forall children : list (tree A), map (tree_map (fun x: A => x)) children = children
x: tree A
l: list (tree A)map (tree_map (fun x0 : A => x0)) (x :: l) = x :: lreflexivity.tree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
aux:= forall children : list (tree A), map (tree_map (fun x: A => x)) children = childrenmap (tree_map (fun x : A => x)) [] = []tree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
aux:= forall children : list (tree A), map (tree_map (fun x: A => x)) children = children
x: tree A
l: list (tree A)map (tree_map (fun x0 : A => x0)) (x :: l) = x :: ltree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
aux:= forall children : list (tree A), map (tree_map (fun x: A => x)) children = children
x: tree A
l: list (tree A)tree_map (fun x0 : A => x0) x :: map (tree_map (fun x0 : A => x0)) l = x :: ltree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
aux:= forall children : list (tree A), map (tree_map (fun x: A => x)) children = children
x: tree A
l: list (tree A)tree_map (fun x0 : A => x0) x = xtree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
aux:= forall children : list (tree A), map (tree_map (fun x: A => x)) children = children
x: tree A
l: list (tree A)map (tree_map (fun x0 : A => x0)) l = lapply tree_map_id.tree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
aux:= forall children : list (tree A), map (tree_map (fun x: A => x)) children = children
x: tree A
l: list (tree A)tree_map (fun x0 : A => x0) x = xapply aux.tree_map_id:= forall (A : Type) (t : tree A), tree_map (fun x: A => x) t = t
A: Type
a: A
aux:= forall children : list (tree A), map (tree_map (fun x: A => x)) children = children
x: tree A
l: list (tree A)map (tree_map (fun x0 : A => x0)) l = l
This was rather painful because we had to manually ensure that Rocq recognises the proof as structurally recursive. Ideally we would like to be able to do induction like always. In order to do so, we are going to use fixpoints to prove our own induction principle for trees.
We'll start by copying the type of tree_ind.
We then add an induction hypothesis for all elements of the children list. Note that we again have to use fix to be able to prove it, but we only have to go through that once and for all.
forall (A : Type) (P : tree A -> Prop), (forall (a : A) (l : list (tree A)), (forall x : tree A, In x l -> P x) -> P (node a l)) -> forall t : tree A, P tforall (A : Type) (P : tree A -> Prop), (forall (a : A) (l : list (tree A)), (forall x : tree A, In x l -> P x) -> P (node a l)) -> forall t : tree A, P tA: Type
P: tree A -> Prop
h:= forall (a : A) (l : list (tree A)), (forall x: tree A, In x l -> P x) -> P (node a l)forall t : tree A, P tA: Type
P: tree A -> Prop
h:= forall (a : A) (l : list (tree A)), (forall x: tree A, In x l -> P x) -> P (node a l)
ind:= forall t: tree A, P tforall t : tree A, P tA: Type
P: tree A -> Prop
h:= forall (a : A) (l : list (tree A)), (forall x: tree A, In x l -> P x) -> P (node a l)
ind:= forall t: tree A, P t
a: A
l: list (tree A)P (node a l)A: Type
P: tree A -> Prop
h:= forall (a : A) (l : list (tree A)), (forall x: tree A, In x l -> P x) -> P (node a l)
ind:= forall t: tree A, P t
a: A
l: list (tree A)forall x : tree A, In x l -> P xA: Type
P: tree A -> Prop
h:= forall (a : A) (l : list (tree A)), (forall x: tree A, In x l -> P x) -> P (node a l)
ind:= forall t: tree A, P t
a: Aforall (l : list (tree A)) (x : tree A), In x l -> P xA: Type
P: tree A -> Prop
h:= forall (a : A) (l : list (tree A)), (forall x: tree A, In x l -> P x) -> P (node a l)
ind:= forall t: tree A, P t
a: A
aux:= forall (l : list (tree A)) (x: tree A), In x l -> P xforall (l : list (tree A)) (x : tree A), In x l -> P xA: Type
P: tree A -> Prop
h:= forall (a : A) (l : list (tree A)), (forall x: tree A, In x l -> P x) -> P (node a l)
ind:= forall t: tree A, P t
a: A
aux:= forall (l : list (tree A)) (x: tree A), In x l -> P x
l: list (tree A)forall x : tree A, In x l -> P xA: Type
P: tree A -> Prop
h:= forall (a : A) (l : list (tree A)), (forall x: tree A, In x l -> P x) -> P (node a l)
ind:= forall t: tree A, P t
a: A
aux:= forall (l : list (tree A)) (x: tree A), In x l -> P xforall x : tree A, In x [] -> P xA: Type
P: tree A -> Prop
h:= forall (a : A) (l : list (tree A)), (forall x: tree A, In x l -> P x) -> P (node a l)
ind:= forall t: tree A, P t
a: A
aux:= forall (l : list (tree A)) (x: tree A), In x l -> P x
x: tree A
l: list (tree A)forall x0 : tree A, In x0 (x :: l) -> P x0A: Type
P: tree A -> Prop
h:= forall (a : A) (l : list (tree A)), (forall x: tree A, In x l -> P x) -> P (node a l)
ind:= forall t: tree A, P t
a: A
aux:= forall (l : list (tree A)) (x: tree A), In x l -> P xforall x : tree A, In x [] -> P xdestruct Hy.A: Type
P: tree A -> Prop
h:= forall (a : A) (l : list (tree A)), (forall x: tree A, In x l -> P x) -> P (node a l)
ind:= forall t: tree A, P t
a: A
aux:= forall (l : list (tree A)) (x: tree A), In x l -> P x
y: tree A
Hy: In y []P yA: Type
P: tree A -> Prop
h:= forall (a : A) (l : list (tree A)), (forall x: tree A, In x l -> P x) -> P (node a l)
ind:= forall t: tree A, P t
a: A
aux:= forall (l : list (tree A)) (x: tree A), In x l -> P x
x: tree A
l: list (tree A)forall x0 : tree A, In x0 (x :: l) -> P x0A: Type
P: tree A -> Prop
h:= forall (a : A) (l : list (tree A)), (forall x: tree A, In x l -> P x) -> P (node a l)
ind:= forall t: tree A, P t
a: A
aux:= forall (l : list (tree A)) (x: tree A), In x l -> P x
x: tree A
l: list (tree A)
y: tree A
Hy:= In y (x :: l)P yA: Type
P: tree A -> Prop
h:= forall (a : A) (l : list (tree A)), (forall x: tree A, In x l -> P x) -> P (node a l)
ind:= forall t: tree A, P t
a: A
aux:= forall (l : list (tree A)) (x: tree A), In x l -> P x
x: tree A
l: list (tree A)P xA: Type
P: tree A -> Prop
h:= forall (a : A) (l : list (tree A)), (forall x: tree A, In x l -> P x) -> P (node a l)
ind:= forall t: tree A, P t
a: A
aux:= forall (l : list (tree A)) (x: tree A), In x l -> P x
x: tree A
l: list (tree A)
y: tree A
Hy: In y lP yapply ind.A: Type
P: tree A -> Prop
h:= forall (a : A) (l : list (tree A)), (forall x: tree A, In x l -> P x) -> P (node a l)
ind:= forall t: tree A, P t
a: A
aux:= forall (l : list (tree A)) (x: tree A), In x l -> P x
x: tree A
l: list (tree A)P xA: Type
P: tree A -> Prop
h:= forall (a : A) (l : list (tree A)), (forall x: tree A, In x l -> P x) -> P (node a l)
ind:= forall t: tree A, P t
a: A
aux:= forall (l : list (tree A)) (x: tree A), In x l -> P x
x: tree A
l: list (tree A)
y: tree A
Hy: In y lP yassumption.A: Type
P: tree A -> Prop
h:= forall (a : A) (l : list (tree A)), (forall x: tree A, In x l -> P x) -> P (node a l)
ind:= forall t: tree A, P t
a: A
aux:= forall (l : list (tree A)) (x: tree A), In x l -> P x
x: tree A
l: list (tree A)
y: tree A
Hy: In y lIn y l
Instead of using In, we can also use the Forall predicate.
forall (A : Type) (P : tree A -> Prop), (forall (a : A) (l : list (tree A)), Forall P l -> P (node a l)) -> forall t : tree A, P tforall (A : Type) (P : tree A -> Prop), (forall (a : A) (l : list (tree A)), Forall P l -> P (node a l)) -> forall t : tree A, P tA: Type
P: tree A -> Prop
h:= forall (a : A) (l: list (tree A)), Forall P l -> P (node a l)forall t : tree A, P tA: Type
P: tree A -> Prop
h:= forall (a : A) (l: list (tree A)), Forall P l -> P (node a l)
ind:= forall t: tree A, P tforall t : tree A, P tA: Type
P: tree A -> Prop
h:= forall (a : A) (l: list (tree A)), Forall P l -> P (node a l)
ind:= forall t: tree A, P t
a: A
l: list (tree A)P (node a l)A: Type
P: tree A -> Prop
h:= forall (a : A) (l: list (tree A)), Forall P l -> P (node a l)
ind:= forall t: tree A, P t
a: A
l: list (tree A)Forall P lA: Type
P: tree A -> Prop
h:= forall (a : A) (l: list (tree A)), Forall P l -> P (node a l)
ind:= forall t: tree A, P t
a: Aforall l : list (tree A), Forall P lA: Type
P: tree A -> Prop
h:= forall (a : A) (l: list (tree A)), Forall P l -> P (node a l)
ind:= forall t: tree A, P t
a: A
aux:= forall l: list (tree A), Forall P lforall l : list (tree A), Forall P lA: Type
P: tree A -> Prop
h:= forall (a : A) (l: list (tree A)), Forall P l -> P (node a l)
ind:= forall t: tree A, P t
a: A
aux:= forall l: list (tree A), Forall P l
l: list (tree A)Forall P lA: Type
P: tree A -> Prop
h:= forall (a : A) (l: list (tree A)), Forall P l -> P (node a l)
ind:= forall t: tree A, P t
a: A
aux:= forall l: list (tree A), Forall P lForall P []A: Type
P: tree A -> Prop
h:= forall (a : A) (l: list (tree A)), Forall P l -> P (node a l)
ind:= forall t: tree A, P t
a: A
aux:= forall l: list (tree A), Forall P l
x: tree A
l: list (tree A)Forall P (x :: l)constructor.A: Type
P: tree A -> Prop
h:= forall (a : A) (l: list (tree A)), Forall P l -> P (node a l)
ind:= forall t: tree A, P t
a: A
aux:= forall l: list (tree A), Forall P lForall P []A: Type
P: tree A -> Prop
h:= forall (a : A) (l: list (tree A)), Forall P l -> P (node a l)
ind:= forall t: tree A, P t
a: A
aux:= forall l: list (tree A), Forall P l
x: tree A
l: list (tree A)Forall P (x :: l)A: Type
P: tree A -> Prop
h:= forall (a : A) (l: list (tree A)), Forall P l -> P (node a l)
ind:= forall t: tree A, P t
a: A
aux:= forall l: list (tree A), Forall P l
x: tree A
l: list (tree A)P xA: Type
P: tree A -> Prop
h:= forall (a : A) (l: list (tree A)), Forall P l -> P (node a l)
ind:= forall t: tree A, P t
a: A
aux:= forall l: list (tree A), Forall P l
x: tree A
l: list (tree A)Forall P lapply ind.A: Type
P: tree A -> Prop
h:= forall (a : A) (l: list (tree A)), Forall P l -> P (node a l)
ind:= forall t: tree A, P t
a: A
aux:= forall l: list (tree A), Forall P l
x: tree A
l: list (tree A)P xapply aux.A: Type
P: tree A -> Prop
h:= forall (a : A) (l: list (tree A)), Forall P l -> P (node a l)
ind:= forall t: tree A, P t
a: A
aux:= forall l: list (tree A), Forall P l
x: tree A
l: list (tree A)Forall P l
We can now use this induction principle by hand to prove tree_map_id again. We make use of the pattern tactic, pattern t attempts to rehape the goal so it has the form P t for some P.
forall (A : Type) (t : tree A), tree_map (fun x : A => x) t = tforall (A : Type) (t : tree A), tree_map (fun x : A => x) t = tA: Type
t: tree Atree_map (fun x : A => x) t = tA: Type
t: tree A(fun t0 : tree A => tree_map (fun x : A => x) t0 = t0) tA: Type
t: tree Aforall (a : A) (l : list (tree A)), Forall (fun t0 : tree A => tree_map (fun x : A => x) t0 = t0) l -> tree_map (fun x : A => x) (node a l) = node a lA: Typeforall (a : A) (l : list (tree A)), Forall (fun t : tree A => tree_map (fun x : A => x) t = t) l -> tree_map (fun x : A => x) (node a l) = node a lA: Type
a: A
l: list (tree A)
h:= Forall (fun t : tree A => tree_map (fun x: A => x) t = t) ltree_map (fun x : A => x) (node a l) = node a lA: Type
a: A
l: list (tree A)
h:= Forall (fun t : tree A => tree_map (fun x: A => x) t = t) lnode a (map (tree_map (fun x : A => x)) l) = node a lA: Type
a: A
l: list (tree A)
h:= Forall (fun t : tree A => tree_map (fun x: A => x) t = t) lmap (tree_map (fun x : A => x)) l = lA: Type
a: Amap (tree_map (fun x : A => x)) [] = []A: Type
a: A
tr: tree A
l: list (tree A)
e:= tree_map (fun x: A => x) tr = tr
h:= Forall (fun t : tree A => tree_map (fun x: A => x) t = t) l
ih:= map (tree_map (fun x: A => x)) l = lmap (tree_map (fun x : A => x)) (tr :: l) = tr :: lreflexivity.A: Type
a: Amap (tree_map (fun x : A => x)) [] = []A: Type
a: A
tr: tree A
l: list (tree A)
e:= tree_map (fun x: A => x) tr = tr
h:= Forall (fun t : tree A => tree_map (fun x: A => x) t = t) l
ih:= map (tree_map (fun x: A => x)) l = lmap (tree_map (fun x : A => x)) (tr :: l) = tr :: lA: Type
a: A
tr: tree A
l: list (tree A)
e:= tree_map (fun x: A => x) tr = tr
h:= Forall (fun t : tree A => tree_map (fun x: A => x) t = t) l
ih:= map (tree_map (fun x: A => x)) l = ltree_map (fun x : A => x) tr :: map (tree_map (fun x : A => x)) l = tr :: lA: Type
a: A
tr: tree A
l: list (tree A)
e:= tree_map (fun x: A => x) tr = tr
h:= Forall (fun t : tree A => tree_map (fun x: A => x) t = t) l
ih:= map (tree_map (fun x: A => x)) l = ltree_map (fun x : A => x) tr = trA: Type
a: A
tr: tree A
l: list (tree A)
e:= tree_map (fun x: A => x) tr = tr
h:= Forall (fun t : tree A => tree_map (fun x: A => x) t = t) l
ih:= map (tree_map (fun x: A => x)) l = lmap (tree_map (fun x : A => x)) l = lapply e.A: Type
a: A
tr: tree A
l: list (tree A)
e:= tree_map (fun x: A => x) tr = tr
h:= Forall (fun t : tree A => tree_map (fun x: A => x) t = t) l
ih:= map (tree_map (fun x: A => x)) l = ltree_map (fun x : A => x) tr = trapply ih.A: Type
a: A
tr: tree A
l: list (tree A)
e:= tree_map (fun x: A => x) tr = tr
h:= Forall (fun t : tree A => tree_map (fun x: A => x) t = t) l
ih:= map (tree_map (fun x: A => x)) l = lmap (tree_map (fun x : A => x)) l = l
In fact, we can still use the induction tactic to do it. And it's much nicer. This is done with the special using keyword as below.
forall (A : Type) (t : tree A), tree_map (fun x : A => x) t = tforall (A : Type) (t : tree A), tree_map (fun x : A => x) t = tA: Type
t: tree Atree_map (fun x : A => x) t = tA: Type
a: A
l: list (tree A)
ihl:= Forall (fun t : tree A => tree_map (fun x: A => x) t = t) ltree_map (fun x : A => x) (node a l) = node a lA: Type
a: A
l: list (tree A)
ihl:= Forall (fun t : tree A => tree_map (fun x: A => x) t = t) lnode a (map (tree_map (fun x : A => x)) l) = node a lA: Type
a: A
l: list (tree A)
ihl:= Forall (fun t : tree A => tree_map (fun x: A => x) t = t) lmap (tree_map (fun x : A => x)) l = lA: Type
a: Amap (tree_map (fun x : A => x)) [] = []A: Type
a: A
x: tree A
l: list (tree A)
H:= tree_map (fun x: A => x) x = x
ihl:= Forall (fun t : tree A => tree_map (fun x: A => x) t = t) l
IHihl:= map (tree_map (fun x: A => x)) l = lmap (tree_map (fun x0 : A => x0)) (x :: l) = x :: lreflexivity.A: Type
a: Amap (tree_map (fun x : A => x)) [] = []A: Type
a: A
x: tree A
l: list (tree A)
H:= tree_map (fun x: A => x) x = x
ihl:= Forall (fun t : tree A => tree_map (fun x: A => x) t = t) l
IHihl:= map (tree_map (fun x: A => x)) l = lmap (tree_map (fun x0 : A => x0)) (x :: l) = x :: lA: Type
a: A
x: tree A
l: list (tree A)
H:= tree_map (fun x: A => x) x = x
ihl:= Forall (fun t : tree A => tree_map (fun x: A => x) t = t) l
IHihl:= map (tree_map (fun x: A => x)) l = ltree_map (fun x0 : A => x0) x :: map (tree_map (fun x0 : A => x0)) l = x :: lall: assumption.A: Type
a: A
x: tree A
l: list (tree A)
H:= tree_map (fun x: A => x) x = x
ihl:= Forall (fun t : tree A => tree_map (fun x: A => x) t = t) l
IHihl:= map (tree_map (fun x: A => x)) l = ltree_map (fun x0 : A => x0) x = xA: Type
a: A
x: tree A
l: list (tree A)
H:= tree_map (fun x: A => x) x = x
ihl:= Forall (fun t : tree A => tree_map (fun x: A => x) t = t) l
IHihl:= map (tree_map (fun x: A => x)) l = lmap (tree_map (fun x0 : A => x0)) l = l
Size recursion
We have already seen size induction earlier. We can use the same idea to prove a size recursion principle:
X: Type
size: X -> nat
P: X -> Type(forall x : X, (forall y : X, size y < size x -> P y) -> P x) -> forall x : X, P xX: Type
size: X -> nat
P: X -> Type(forall x : X, (forall y : X, size y < size x -> P y) -> P x) -> forall x : X, P xX: Type
size: X -> nat
P: X -> Type
D:= forall x : X, (forall y: X, size y < size x -> P y) -> P x
x: XP xX: Type
size: X -> nat
P: X -> Type
D:= forall x : X, (forall y: X, size y < size x -> P y) -> P x
x: Xforall y : X, size y < size x -> P yX: Type
size: X -> nat
P: X -> Type
D:= forall x : X, (forall y: X, size y < size x -> P y) -> P x
x: X(forall (n : nat) (y : X), size y < n -> P y) -> forall y : X, size y < size x -> P yX: Type
size: X -> nat
P: X -> Type
D:= forall x : X, (forall y: X, size y < size x -> P y) -> P x
x: Xforall (n : nat) (y : X), size y < n -> P yeauto.X: Type
size: X -> nat
P: X -> Type
D:= forall x : X, (forall y: X, size y < size x -> P y) -> P x
x: X(forall (n : nat) (y : X), size y < n -> P y) -> forall y : X, size y < size x -> P yX: Type
size: X -> nat
P: X -> Type
D:= forall x : X, (forall y: X, size y < size x -> P y) -> P x
x: Xforall (n : nat) (y : X), size y < n -> P yX: Type
size: X -> nat
P: X -> Type
D:= forall x : X, (forall y: X, size y < size x -> P y) -> P xforall (n : nat) (y : X), size y < n -> P yX: Type
size: X -> nat
P: X -> Type
D:= forall x : X, (forall y: X, size y < size x -> P y) -> P x
n: nat
y: X
E: size y < nP yX: Type
size: X -> nat
P: X -> Type
D:= forall x : X, (forall y: X, size y < size x -> P y) -> P x
y: X
E: size y < 0P yX: Type
size: X -> nat
P: X -> Type
D:= forall x : X, (forall y: X, size y < size x -> P y) -> P x
n: nat
y: X
E: size y < S n
ih:= forall y: X, size y < n -> P yP ylia.X: Type
size: X -> nat
P: X -> Type
D:= forall x : X, (forall y: X, size y < size x -> P y) -> P x
y: X
E: size y < 0P yX: Type
size: X -> nat
P: X -> Type
D:= forall x : X, (forall y: X, size y < size x -> P y) -> P x
n: nat
y: X
E: size y < S n
ih:= forall y: X, size y < n -> P yP yX: Type
size: X -> nat
P: X -> Type
D:= forall x : X, (forall y: X, size y < size x -> P y) -> P x
n: nat
y: X
E: size y < S n
ih:= forall y: X, size y < n -> P yforall y0 : X, size y0 < size y -> P y0X: Type
size: X -> nat
P: X -> Type
D:= forall x : X, (forall y: X, size y < size x -> P y) -> P x
n: nat
y: X
E: size y < S n
ih:= forall y: X, size y < n -> P y
x: X
F: size x < size yP xlia.X: Type
size: X -> nat
P: X -> Type
D:= forall x : X, (forall y: X, size y < size x -> P y) -> P x
n: nat
y: X
E: size y < S n
ih:= forall y: X, size y < n -> P y
x: X
F: size x < size ysize x < n
Well-founded induction
The last topic is about doing induction / recursion along any well-founded order. We define well-foundedness of an order on a point as the so-called accessibility predicate.
Section Well_founded.We work with a type and a relation on it. Note. We do not require this relation to be an equivalence relation or anything actually. This isn't necessary.
A point x is accessible when all its predecessors in the relation are accessibe as well.
A: Type
R: A -> A -> Prop
x: AAcc x <-> (forall y : A, R y x -> Acc y)A: Type
R: A -> A -> Prop
x: AAcc x <-> (forall y : A, R y x -> Acc y)A: Type
R: A -> A -> Prop
x: AAcc x -> forall y : A, R y x -> Acc yA: Type
R: A -> A -> Prop
x: A(forall y : A, R y x -> Acc y) -> Acc xA: Type
R: A -> A -> Prop
x: AAcc x -> forall y : A, R y x -> Acc yexact h.A: Type
R: A -> A -> Prop
x: A
h:= forall y: A, R y x -> Acc yforall y : A, R y x -> Acc yA: Type
R: A -> A -> Prop
x: A(forall y : A, R y x -> Acc y) -> Acc xA: Type
R: A -> A -> Prop
x: A
H:= forall y: A, R y x -> Acc yAcc xassumption.A: Type
R: A -> A -> Prop
x: A
H:= forall y: A, R y x -> Acc yforall y : A, R y x -> Acc yEnd Well_founded.
Now behold the return of the subsingleton criterion! Acc indeed verifies it: it has exactly one constructor Acc_intro which itself has two arguments, x : A and h : forall y, R y x -> Acc y. The first one is a parameter so it doesn't count, and the second one is a proof (because, remember, ∀ quantifiers ending in Prop land in Prop as well).
This means that we get an induction principle with a motive in Type:
It says that when proving P x for accessible x, one can assume P y for any R-smaller y.
We can prove that accessible points are not contained in loops:
X: Type
R: X -> X -> Prop
x: XAcc R x -> ~ (exists y : X, R x y /\ R y x)X: Type
R: X -> X -> Prop
x: XAcc R x -> ~ (exists y : X, R x y /\ R y x)X: Type
R: X -> X -> Prop
x: X
H: Acc R x~ (exists y : X, R x y /\ R y x)firstorder.X: Type
R: X -> X -> Prop
x: X
H:= forall y: X, R y x -> Acc R y
ih:= forall y : X, R y x -> ~ (exists y0: X, R y y0 /\ R y0 y)~ (exists y : X, R x y /\ R y x)
And that accessible points are not the start of an infinite descending path.
X: Type
R: X -> X -> Prop
x: XAcc R x -> ~ (exists path : nat -> X, path 0 = x /\ (forall n : nat, R (path (S n)) (path n)))X: Type
R: X -> X -> Prop
x: XAcc R x -> ~ (exists path : nat -> X, path 0 = x /\ (forall n : nat, R (path (S n)) (path n)))X: Type
R: X -> X -> Prop
x: X
H: Acc R x~ (exists path : nat -> X, path 0 = x /\ (forall n : nat, R (path (S n)) (path n)))X: Type
R: X -> X -> Prop
x: X
H:= forall y: X, R y x -> Acc R y
ih:= forall y : X, R y x -> ~ (exists path : nat -> X, path 0 = y /\ (forall n: nat, R (path (S n)) (path n)))~ (exists path : nat -> X, path 0 = x /\ (forall n : nat, R (path (S n)) (path n)))X: Type
R: X -> X -> Prop
x: X
H:= forall y: X, R y x -> Acc R y
ih:= forall y : X, R y x -> ~ (exists path : nat -> X, path 0 = y /\ (forall n: nat, R (path (S n)) (path n)))
path: nat -> X
H1: path 0 = x
H2:= forall n: nat, R (path (S n)) (path n)FalseX: Type
R: X -> X -> Prop
path: nat -> X
ih:= forall y : X, R y (path 0) -> ~ (exists path : nat -> X, path 0 = y /\ (forall n: nat, R (path (S n)) (path n)))
H:= forall y: X, R y (path 0) -> Acc R y
H2:= forall n: nat, R (path (S n)) (path n)FalseX: Type
R: X -> X -> Prop
path: nat -> X
ih:= forall y : X, R y (path 0) -> ~ (exists path : nat -> X, path 0 = y /\ (forall n: nat, R (path (S n)) (path n)))
H:= forall y: X, R y (path 0) -> Acc R y
H2:= forall n: nat, R (path (S n)) (path n)R ?y (path 0)X: Type
R: X -> X -> Prop
path: nat -> X
ih:= forall y : X, R y (path 0) -> ~ (exists path : nat -> X, path 0 = y /\ (forall n: nat, R (path (S n)) (path n)))
H:= forall y: X, R y (path 0) -> Acc R y
H2:= forall n: nat, R (path (S n)) (path n)exists path0 : nat -> X, path0 0 = ?y /\ (forall n : nat, R (path0 (S n)) (path0 n))eapply H2.X: Type
R: X -> X -> Prop
path: nat -> X
ih:= forall y : X, R y (path 0) -> ~ (exists path : nat -> X, path 0 = y /\ (forall n: nat, R (path (S n)) (path n)))
H:= forall y: X, R y (path 0) -> Acc R y
H2:= forall n: nat, R (path (S n)) (path n)R ?y (path 0)X: Type
R: X -> X -> Prop
path: nat -> X
ih:= forall y : X, R y (path 0) -> ~ (exists path : nat -> X, path 0 = y /\ (forall n: nat, R (path (S n)) (path n)))
H:= forall y: X, R y (path 0) -> Acc R y
H2:= forall n: nat, R (path (S n)) (path n)exists path0 : nat -> X, path0 0 = path 1 /\ (forall n : nat, R (path0 (S n)) (path0 n))firstorder.X: Type
R: X -> X -> Prop
path: nat -> X
ih:= forall y : X, R y (path 0) -> ~ (exists path : nat -> X, path 0 = y /\ (forall n: nat, R (path (S n)) (path n)))
H:= forall y: X, R y (path 0) -> Acc R y
H2:= forall n: nat, R (path (S n)) (path n)path 1 = path 1 /\ (forall n : nat, R (path (S (S n))) (path (S n)))
To conclude this lecture, we give a proof that one can use recursion on proofs to write a function that extracts the witness from a proof of exists n, f n = true.
This proof can be given using different ways. We show the easiest approach here, and leave a variant using Acc as exercise.
From Stdlib Require Import Lia.We start by giving a predicate least P n k that says that k is the least natural (above n) that verifies predicate P.
Section WO.
The key is in the definition of a guardedness predicate which is in Prop` (so we can eliminate exist into it) and which verifies the subsingleton criterion (so we can eliminate it into Type).
f: nat -> bool
n: natG n -> {k : nat | least (fun k0 : nat => f k0 = true) n k}f: nat -> bool
n: natG n -> {k : nat | least (fun k0 : nat => f k0 = true) n k}f: nat -> bool
n: nat
ih:= f n = false -> {k : nat | least (fun k0: nat => f k0 = true) (S n) k}{k : nat | least (fun k0 : nat => f k0 = true) n k}f: nat -> bool
n: nat
e: f n = true
ih:= true = false -> {k : nat | least (fun k0: nat => f k0 = true) (S n) k}{k : nat | least (fun k0 : nat => f k0 = true) n k}f: nat -> bool
n: nat
e: f n = false
ih:= false = false -> {k : nat | least (fun k0: nat => f k0 = true) (S n) k}{k : nat | least (fun k0 : nat => f k0 = true) n k}f: nat -> bool
n: nat
e: f n = true
ih:= true = false -> {k : nat | least (fun k0: nat => f k0 = true) (S n) k}{k : nat | least (fun k0 : nat => f k0 = true) n k}f: nat -> bool
n: nat
e: f n = true
ih:= true = false -> {k : nat | least (fun k0: nat => f k0 = true) (S n) k}least (fun k : nat => f k = true) n neauto.f: nat -> bool
n: nat
e: f n = true
ih:= true = false -> {k : nat | least (fun k0: nat => f k0 = true) (S n) k}n <= n /\ f n = true /\ (forall i : nat, n <= i -> f i = true -> n <= i)f: nat -> bool
n: nat
e: f n = false
ih:= false = false -> {k : nat | least (fun k0: nat => f k0 = true) (S n) k}{k : nat | least (fun k0 : nat => f k0 = true) n k}f: nat -> bool
n: nat
e: f n = false
ih:= false = false -> {k : nat | least (fun k0: nat => f k0 = true) (S n) k}
k: nat
hle: S n <= k
hk: f k = true
hleast:= forall i: nat, S n <= i -> f i = true -> k <= i{k0 : nat | least (fun k1 : nat => f k1 = true) n k0}f: nat -> bool
n: nat
e: f n = false
ih:= false = false -> {k : nat | least (fun k0: nat => f k0 = true) (S n) k}
k: nat
hle: S n <= k
hk: f k = true
hleast:= forall i: nat, S n <= i -> f i = true -> k <= ileast (fun k0 : nat => f k0 = true) n kf: nat -> bool
n: nat
e: f n = false
ih:= false = false -> {k : nat | least (fun k0: nat => f k0 = true) (S n) k}
k: nat
hle: S n <= k
hk: f k = true
hleast:= forall i: nat, S n <= i -> f i = true -> k <= in <= kf: nat -> bool
n: nat
e: f n = false
ih:= false = false -> {k : nat | least (fun k0: nat => f k0 = true) (S n) k}
k: nat
hle: S n <= k
hk: f k = true
hleast:= forall i: nat, S n <= i -> f i = true -> k <= if k = true /\ (forall i : nat, n <= i -> f i = true -> k <= i)f: nat -> bool
n: nat
e: f n = false
ih:= false = false -> {k : nat | least (fun k0: nat => f k0 = true) (S n) k}
k: nat
hle: S n <= k
hk: f k = true
hleast:= forall i: nat, S n <= i -> f i = true -> k <= in <= kf: nat -> bool
n: nat
e: f n = false
ih:= false = false -> {k : nat | least (fun k0: nat => f k0 = true) (S n) k}
k: nat
hle: S n <= k
hk: f k = true
hleast:= forall i: nat, S n <= i -> f i = true -> k <= if k = truef: nat -> bool
n: nat
e: f n = false
ih:= false = false -> {k : nat | least (fun k0: nat => f k0 = true) (S n) k}
k: nat
hle: S n <= k
hk: f k = true
hleast:= forall i: nat, S n <= i -> f i = true -> k <= iforall i : nat, n <= i -> f i = true -> k <= ilia.f: nat -> bool
n: nat
e: f n = false
ih:= false = false -> {k : nat | least (fun k0: nat => f k0 = true) (S n) k}
k: nat
hle: S n <= k
hk: f k = true
hleast:= forall i: nat, S n <= i -> f i = true -> k <= in <= kassumption.f: nat -> bool
n: nat
e: f n = false
ih:= false = false -> {k : nat | least (fun k0: nat => f k0 = true) (S n) k}
k: nat
hle: S n <= k
hk: f k = true
hleast:= forall i: nat, S n <= i -> f i = true -> k <= if k = truef: nat -> bool
n: nat
e: f n = false
ih:= false = false -> {k : nat | least (fun k0: nat => f k0 = true) (S n) k}
k: nat
hle: S n <= k
hk: f k = true
hleast:= forall i: nat, S n <= i -> f i = true -> k <= iforall i : nat, n <= i -> f i = true -> k <= if: nat -> bool
n: nat
e: f n = false
ih:= false = false -> {k : nat | least (fun k0: nat => f k0 = true) (S n) k}
k: nat
hle: S n <= k
hk: f k = true
hleast:= forall i: nat, S n <= i -> f i = true -> k <= i
i: nat
hi: n <= if i = true -> k <= if: nat -> bool
n: nat
e: f n = false
ih:= false = false -> {k : nat | least (fun k0: nat => f k0 = true) (S n) k}
k: nat
hle: S n <= k
hk: f k = true
hleast:= forall i: nat, S n <= i -> f i = true -> k <= i
i: nat
hi: n <= i
H: n = if i = true -> k <= if: nat -> bool
n: nat
e: f n = false
ih:= false = false -> {k : nat | least (fun k0: nat => f k0 = true) (S n) k}
k: nat
hle: S n <= k
hk: f k = true
hleast:= forall i: nat, S n <= i -> f i = true -> k <= i
i: nat
hi: n <= i
m: nat
H: n <= m
H0: S m = if (S m) = true -> k <= S mcongruence.f: nat -> bool
n: nat
e: f n = false
ih:= false = false -> {k : nat | least (fun k0: nat => f k0 = true) (S n) k}
k: nat
hle: S n <= k
hk: f k = true
hleast:= forall i: nat, S n <= i -> f i = true -> k <= i
i: nat
hi: n <= i
H: n = if i = true -> k <= if: nat -> bool
n: nat
e: f n = false
ih:= false = false -> {k : nat | least (fun k0: nat => f k0 = true) (S n) k}
k: nat
hle: S n <= k
hk: f k = true
hleast:= forall i: nat, S n <= i -> f i = true -> k <= i
i: nat
hi: n <= i
m: nat
H: n <= m
H0: S m = if (S m) = true -> k <= S mlia.f: nat -> bool
n: nat
e: f n = false
ih:= false = false -> {k : nat | least (fun k0: nat => f k0 = true) (S n) k}
k: nat
hle: S n <= k
hk: f k = true
hleast:= forall i: nat, S n <= i -> f i = true -> k <= i
i: nat
hi: n <= i
m: nat
H: n <= m
H0: S m = iS n <= S mf: nat -> bool
n: natG n -> G 0f: nat -> bool
n: natG n -> G 0f: nat -> boolG 0 -> G 0f: nat -> bool
n: nat
ih: G n -> G 0G (S n) -> G 0auto.f: nat -> boolG 0 -> G 0f: nat -> bool
n: nat
ih: G n -> G 0G (S n) -> G 0f: nat -> bool
n: nat
ih: G n -> G 0
h: G (S n)G 0f: nat -> bool
n: nat
ih: G n -> G 0
h: G (S n)G nauto.f: nat -> bool
n: nat
ih: G n -> G 0
h: G (S n)f n = false -> G (S n)f: nat -> bool(exists n : nat, f n = true) -> {n : nat | least (fun n0 : nat => f n0 = true) 0 n}f: nat -> bool(exists n : nat, f n = true) -> {n : nat | least (fun n0 : nat => f n0 = true) 0 n}f: nat -> bool
h:= exists n: nat, f n = true{n : nat | least (fun n0 : nat => f n0 = true) 0 n}f: nat -> bool
h:= exists n: nat, f n = trueG 0f: nat -> bool
n: nat
h: f n = trueG 0f: nat -> bool
n: nat
h: f n = trueG nf: nat -> bool
n: nat
h: f n = truef n = false -> G (S n)discriminate.f: nat -> bool
n: nat
h: f n = truetrue = false -> G (S n)End WO.
Gallery of inductive types and corresponding inductive principles
We do not believe giving you the recipe in general is going to be useful, instead we think showing you representative examples together is going to be more helpful. So here we go.
Lists
Lists are an inductive type (Type) have one uniform parameter A : Type, and two constructors nil and cons.
As it's a type, it can be eliminated to produce both Type and Prop, hence we have the two following induction principles.
The motive is thus of the form P : list A -> Type or P : list A -> Prop. Note that A is a uniform parameter so it is quantified before the motive, which is why it can mention it. The conclusion will then be forall (l : list A), P l.
The induction principles has two branches, one for nil and one for cons. Because nil : list A the corresponding branch is P nil. Because cons : A -> list A -> list A the corresponding branch is forall (a : A) (l : list A), P l -> P (cons a l). To get it, you start from the type of the constructor, and for each recursive invocation of list A you additionally get some induction hypothesis P l. The return type list A is then replaced by P (cons a l), i.e. the motive applied to the constructor itself.
Evenness
We have also seen the even predicate which is a proposition (Prop). It takes no parameter, has one index n : nat and two constructors.
Since it has two constructors, it doesn't satisfy the subsingleton criterion and thus it produces only even_ind but no even_rect.
Since the sort is Prop, the motive will not depend on the proof of even n, only on n, so it will be P : nat -> Prop.
Otherwise the recipe is the same, you just don't need to apply P to the constructor, only to the value of the index, so in one branch P 0 and in the other P (S (S n)). The conclusion will be forall n, even n -> P n. Even if P doesn't talk about the proof of even n, it is still required.
Accessibility
Acc has three parameters: two uniform parameters A and R and one non-uniform parameter x (it stays x in the conclusion, but in the argument to Acc_intro it is instiated by y). It has no index.
Because it has one constructor with only one non-parameter argument which
happens to be a proof (forall y, R y x -> Acc A R y has type Prop), it
satisfies the subsingleton criterion.