Week 4: Inductive Propositions and Inductive Reasoning
- Author
- License
No redistribution allowed (usage by permission in CS-428).
Introduction
So far, we've seen how to define Coq functions, and how to define simple propositions.
Examples of propositions included equality (for instance, proving that the result of a function call is equal to something).
But we've also been constructing propositions with quantifiers (exists
, forall
), conjunction, disjunction, implication...
In this lecture, we look at yet another way to define propositions: the inductive ones.
We'll see how to define these inductive propositions.
We'll see how these can be particularly convenient to express propositions that were not so easy to define using our previous tools.
We'll see how to reason about them, by case analysis, inversion or induction, generalizing the induction principles we've been using before.
Revisiting Eval
Remember the simple language of arithmetic expressions with variables defined in Week 1.
Notation var := string. Inductive arith : Set := | Const (n : nat) | Var (x : var) | Plus (e1 e2 : arith) | Times (e1 e2 : arith). Example ex1 := Const 42. Example ex2 := Plus (Const 1) (Times (Var "x") (Var "y")).
It was pretty easy to define natural semantics for these expressions with a simple function. Of course, to evaluate variables, we needed a "valuation", a function defining the value of each variable.
The result is a function. Coq can compute it.
Notation val := (var -> nat). Fixpoint eval (vn: val) (e: arith) : nat := match e with | Const n => n | Var v => vn v | Plus e1 e2 => eval vn e1 + eval vn e2 | Times e1 e2 => eval vn e1 * eval vn e2 end. Example eval_example: nat := (eval (fun _ => 0) (Plus (Const 58) (Const 12))).
If you have some experience in defining programming language semantics, you might be used to another style of definition, with horizontal bars. (Blackboard example). Let's try to write the same thing in Coq:
Inductive ieval: val -> arith -> nat -> Prop :=
| i_const:
forall (vn:val) (n:nat),
ieval vn (Const n) n
| i_var:
forall (vn:val) (v:var) (n:nat)
(EVAL_VAR: vn v = n),
ieval vn (Var v) n
| i_plus:
forall (vn:val) (e1 e2:arith) (n1 n2:nat)
(EVAL_LEFT: ieval vn e1 n1)
(EVAL_RIGHT: ieval vn e2 n2),
ieval vn (Plus e1 e2) (n1 + n2)
| i_times:
forall (vn:val) (e1 e2:arith) (n1 n2:nat)
(EVAL_LEFT: ieval vn e1 n1)
(EVAL_RIGHT: ieval vn e2 n2),
ieval vn (Times e1 e2) (n1 * n2).
The process of turning the blackboard rules into Coq rules has been straightforward:
rule names become constructor names
hypotheses above the bar become universally quantified
conclusions below the bar are conclusions of each constructor.
Note that naming hypotheses is optional and could be replaced by an implication.
But what does this represent in Coq?
We have just defined an inductive proposition (note the Inductive
and Prop
keywords).
Just like a = b
was a proposition, now ieval vn e n
is a proposition.
What do we love to do with propositions? Prove them!
ieval (fun _ : var => 0) (Plus (Const 58) (Const 12)) 70(* each constructor can be applied, like a blackboard rule *)ieval (fun _ : var => 0) (Plus (Const 58) (Const 12)) 70(* the rule expects the Plus to evaluate to an addition, let's show that 70 is in fact an addition *)ieval (fun _ : var => 0) (Plus (Const 58) (Const 12)) 70ieval (fun _ : var => 0) (Plus (Const 58) (Const 12)) (58 + 12)(* apply (i_plus _ _ _ 58 12). would have also worked *) (* our rule had two premises. we need to prove them both *)ieval (fun _ : var => 0) (Const 58) 58ieval (fun _ : var => 0) (Const 12) 12apply i_const.ieval (fun _ : var => 0) (Const 58) 58apply i_const. Qed.ieval (fun _ : var => 0) (Const 12) 12
This is a start. But we can prove more interesting properties. For instance, let's prove the following property:
forall (vn : val) (e : arith) (n : nat), eval vn e = n -> ieval vn e nforall (vn : val) (e : arith) (n : nat), eval vn e = n -> ieval vn e nvn: val
e: arithforall n : nat, eval vn e = n -> ieval vn e nvn: val
n, n0: nat
H: n = n0ieval vn (Const n) n0vn: val
x: var
n: nat
H: vn x = nieval vn (Var x) nvn: val
e1, e2: arith
IHe1: forall n : nat, eval vn e1 = n -> ieval vn e1 n
IHe2: forall n : nat, eval vn e2 = n -> ieval vn e2 n
n: nat
H: eval vn e1 + eval vn e2 = nieval vn (Plus e1 e2) nvn: val
e1, e2: arith
IHe1: forall n : nat, eval vn e1 = n -> ieval vn e1 n
IHe2: forall n : nat, eval vn e2 = n -> ieval vn e2 n
n: nat
H: eval vn e1 * eval vn e2 = nieval vn (Times e1 e2) nvn: val
n, n0: nat
H: n = n0ieval vn (Const n) n0apply i_const.vn: val
n0: natieval vn (Const n0) n0vn: val
x: var
n: nat
H: vn x = nieval vn (Var x) nassumption.vn: val
x: var
n: nat
H: vn x = nvn x = nvn: val
e1, e2: arith
IHe1: forall n : nat, eval vn e1 = n -> ieval vn e1 n
IHe2: forall n : nat, eval vn e2 = n -> ieval vn e2 n
n: nat
H: eval vn e1 + eval vn e2 = nieval vn (Plus e1 e2) nvn: val
e1, e2: arith
IHe1: forall n : nat, eval vn e1 = n -> ieval vn e1 n
IHe2: forall n : nat, eval vn e2 = n -> ieval vn e2 n
n: nat
H: eval vn e1 + eval vn e2 = nieval vn (Plus e1 e2) (eval vn e1 + eval vn e2)vn: val
e1, e2: arith
IHe1: forall n : nat, eval vn e1 = n -> ieval vn e1 n
IHe2: forall n : nat, eval vn e2 = n -> ieval vn e2 n
n: nat
H: eval vn e1 + eval vn e2 = nieval vn e1 (eval vn e1)vn: val
e1, e2: arith
IHe1: forall n : nat, eval vn e1 = n -> ieval vn e1 n
IHe2: forall n : nat, eval vn e2 = n -> ieval vn e2 n
n: nat
H: eval vn e1 + eval vn e2 = nieval vn e2 (eval vn e2)vn: val
e1, e2: arith
IHe1: forall n : nat, eval vn e1 = n -> ieval vn e1 n
IHe2: forall n : nat, eval vn e2 = n -> ieval vn e2 n
n: nat
H: eval vn e1 + eval vn e2 = nieval vn e1 (eval vn e1)reflexivity.vn: val
e1, e2: arith
IHe1: forall n : nat, eval vn e1 = n -> ieval vn e1 n
IHe2: forall n : nat, eval vn e2 = n -> ieval vn e2 n
n: nat
H: eval vn e1 + eval vn e2 = neval vn e1 = eval vn e1vn: val
e1, e2: arith
IHe1: forall n : nat, eval vn e1 = n -> ieval vn e1 n
IHe2: forall n : nat, eval vn e2 = n -> ieval vn e2 n
n: nat
H: eval vn e1 + eval vn e2 = nieval vn e2 (eval vn e2)reflexivity.vn: val
e1, e2: arith
IHe1: forall n : nat, eval vn e1 = n -> ieval vn e1 n
IHe2: forall n : nat, eval vn e2 = n -> ieval vn e2 n
n: nat
H: eval vn e1 + eval vn e2 = neval vn e2 = eval vn e2vn: val
e1, e2: arith
IHe1: forall n : nat, eval vn e1 = n -> ieval vn e1 n
IHe2: forall n : nat, eval vn e2 = n -> ieval vn e2 n
n: nat
H: eval vn e1 * eval vn e2 = nieval vn (Times e1 e2) nvn: val
e1, e2: arith
IHe1: forall n : nat, eval vn e1 = n -> ieval vn e1 n
IHe2: forall n : nat, eval vn e2 = n -> ieval vn e2 n
n: nat
H: eval vn e1 * eval vn e2 = nieval vn (Times e1 e2) (eval vn e1 * eval vn e2)vn: val
e1, e2: arith
IHe1: forall n : nat, eval vn e1 = n -> ieval vn e1 n
IHe2: forall n : nat, eval vn e2 = n -> ieval vn e2 n
n: nat
H: eval vn e1 * eval vn e2 = nieval vn e1 (eval vn e1)vn: val
e1, e2: arith
IHe1: forall n : nat, eval vn e1 = n -> ieval vn e1 n
IHe2: forall n : nat, eval vn e2 = n -> ieval vn e2 n
n: nat
H: eval vn e1 * eval vn e2 = nieval vn e2 (eval vn e2)vn: val
e1, e2: arith
IHe1: forall n : nat, eval vn e1 = n -> ieval vn e1 n
IHe2: forall n : nat, eval vn e2 = n -> ieval vn e2 n
n: nat
H: eval vn e1 * eval vn e2 = nieval vn e1 (eval vn e1)reflexivity.vn: val
e1, e2: arith
IHe1: forall n : nat, eval vn e1 = n -> ieval vn e1 n
IHe2: forall n : nat, eval vn e2 = n -> ieval vn e2 n
n: nat
H: eval vn e1 * eval vn e2 = neval vn e1 = eval vn e1vn: val
e1, e2: arith
IHe1: forall n : nat, eval vn e1 = n -> ieval vn e1 n
IHe2: forall n : nat, eval vn e2 = n -> ieval vn e2 n
n: nat
H: eval vn e1 * eval vn e2 = nieval vn e2 (eval vn e2)reflexivity. Qed. (* let's make it a bit more automated *)vn: val
e1, e2: arith
IHe1: forall n : nat, eval vn e1 = n -> ieval vn e1 n
IHe2: forall n : nat, eval vn e2 = n -> ieval vn e2 n
n: nat
H: eval vn e1 * eval vn e2 = neval vn e2 = eval vn e2forall (vn : val) (e : arith) (n : nat), eval vn e = n -> ieval vn e ninduction e; intros; subst; constructor; auto. (* constructor applies the first inductive rule it can apply! *) Qed.forall (vn : val) (e : arith) (n : nat), eval vn e = n -> ieval vn e n
We could expect doing the other implication... but we'll wait a bit for now.
You might think this is a bit more painful than just writing the evaluation function, and for that particular case you may be right. But let's now consider some variations.
What if some of my variables are undefined? In my valuation function, we'll represent this with an option.
Notation valop := (var -> option nat). Example vo:valop := (fun v => match v with | "x" => Some 17 | "y" => Some 3 | _ => None end).
What should be the behavior of arithmetic expressions containing such undefined variables? First, we could just say that the expression does not evaluate at all. In our functional version of eval, we could do this with an option (left as exercise).
What about our inductive proposition?
Inductive ioeval: valop -> arith -> nat -> Prop :=
| io_const:
forall (vn:valop) (n:nat),
ioeval vn (Const n) n
| io_var:
forall (vn:valop) (v:var) (n:nat)
(EVAL_VAR: vn v = Some n), (* here we change to a Some *)
ioeval vn (Var v) n
| io_plus:
forall (vn:valop) (e1 e2:arith) (n1 n2:nat)
(EVAL_LEFT: ioeval vn e1 n1)
(EVAL_RIGHT: ioeval vn e2 n2),
ioeval vn (Plus e1 e2) (n1 + n2)
| io_times:
forall (vn:valop) (e1 e2:arith) (n1 n2:nat)
(EVAL_LEFT: ioeval vn e1 n1)
(EVAL_RIGHT: ioeval vn e2 n2),
ioeval vn (Times e1 e2) (n1 * n2).
No further changes are needed.
We simply did not give a meaning to variables v
when vn v = None
.
The arithmetic expression has "blocking semantics", there is no way to construct a proof tree evaluating this expression to any value using that valuation.
In fact we can even prove it.
forall ret : nat, ~ ioeval vo (Var "z") retforall ret : nat, ~ ioeval vo (Var "z") retforall ret : nat, ioeval vo (Var "z") ret -> Falseret: nat
H: ioeval vo (Var "z") retFalseret: nat
H: ioeval vo (Var "z") ret
vn: valop
v: var
n: nat
EVAL_VAR: vo "z" = Some ret
H0: vn = vo
H2: v = "z"
H1: n = retFalseinversion EVAL_VAR. Qed.ret: nat
H: ioeval vo (Var "z") ret
vn: valop
v: var
n: nat
EVAL_VAR: None = Some ret
H0: vn = vo
H2: v = "z"
H1: n = retFalse
But now, consider another variation, non-determinism.
Suppose that now, undefined variable may evaluate to any possible number!
This would be difficult to define in the functional style of eval
.
But how would you adapt the blackboard rules? We can do the same thing in our Coq inductive proposition:
Inductive ineval: valop -> arith -> nat -> Prop :=
| in_const:
forall (vn:valop) (n:nat),
ineval vn (Const n) n
| in_var:
forall (vn:valop) (v:var) (n:nat)
(EVAL_VAR: vn v = Some n),
ineval vn (Var v) n
| in_undef:
forall (vn:valop) (v:var) (n:nat)
(UNDEFINED: vn v = None), (* for any possible n! *)
ineval vn (Var v) n
| in_plus:
forall (vn:valop) (e1 e2:arith) (n1 n2:nat)
(EVAL_LEFT: ineval vn e1 n1)
(EVAL_RIGHT: ineval vn e2 n2),
ineval vn (Plus e1 e2) (n1 + n2)
| in_times:
forall (vn:valop) (e1 e2:arith) (n1 n2:nat)
(EVAL_LEFT: ineval vn e1 n1)
(EVAL_RIGHT: ineval vn e2 n2),
ineval vn (Times e1 e2) (n1 * n2).
We can now prove that a non-deterministic expression like (Var "z")
has several meanings.
ineval vo (Var "z") 623ineval vo (Var "z") 623vo "z" = Nonereflexivity. Qed.None = Noneineval vo (Var "z") (Init.Nat.of_num_uint (Number.UIntDecimal (Decimal.D2 (Decimal.D3 (Decimal.D6 (Decimal.D2 (Decimal.D3 (Decimal.D6 Decimal.Nil))))))))ineval vo (Var "z") (Init.Nat.of_num_uint (Number.UIntDecimal (Decimal.D2 (Decimal.D3 (Decimal.D6 (Decimal.D2 (Decimal.D3 (Decimal.D6 Decimal.Nil))))))))vo "z" = Nonereflexivity. Qed.None = None
Interestingly, we see that these inductive propositions are convenient to define
partial functions (instead of using an option type)
multi-valued functions (or non-deterministic)
In both cases, the idea is that we see such functions as mathematical relations between their input and their output. While Coq functions are expected to be total (each input has an output) and deterministic (only one output), this notion of relation can accommodate broader definitions. Of course, this expressivity comes at the cost of having a function that can actually be computed within Coq. But this is still convenient for models, invariant, semantics, specifications...
We also previously saw that Coq functions were expected to terminate. Is this also a restriction we can lift by using an inductive proposition?
Let us now leave our arithmetic expressions and consider the famous Collatz or Syracuse conjecture.
The Syracuse sequence computes the next natural number in the sequence as follows:
if k (the last number of the sequence) is equal to 1, the sequence is over
if k is even, divide it by 2 and call Syracuse recursively on the result
if k is odd, multiply it by 3, add 1 and call Syracuse recursively on the result.
The sequence is then determined by the first number.
Starting from 12, we get the sequence 12, 6, 3, 10, 5, 16, 8, 4, 2, 1
.
Starting from 19, we get the sequence è19, 58, 29, 88, 44, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1`.
The Collatz conjecture states that this sequence is always finite, in other words that 1 is always reached, for any starting natural number.
To formalize this conjecture in Coq, we might try to define a recursive function that calculates the total number of steps that it takes for such a sequence to reach 1.
(* next number in the Syracuse sequence *) Definition next_syracuse (k : nat) := if even k then div2 k else (3 * k) + 1.
This definition is rejected by Coq's termination checker, since
the argument to the recursive call, next_syracuse n
, is not "obviously
smaller" than n
.
Indeed, this isn't just a pointless limitation: functions in Coq are required to be total, to ensure logical consistency.
Moreover, we can't fix it by devising a more clever termination checker: deciding whether this particular function is total would be equivalent to settling the Collatz conjecture!
Fortunately, there is another way to do it: We can express the concept "reaches 1 eventually" as an inductively defined property of numbers:
Inductive Collatz_holds_for : nat -> Prop :=
| Chf_done : Collatz_holds_for 1
| Chf_more (n : nat) : Collatz_holds_for (next_syracuse n) ->
Collatz_holds_for n.
What we've done here is to use Coq's Inductive
definition
mechanism to characterize the property "Collatz holds for..." by
stating two different ways in which it can hold: (1) Collatz holds
for 1 and (2) if Collatz holds for next_syracuse n
then it holds for
n
.
We can prove that indeed the Collatz conjecture holds for some numbers:
Collatz_holds_for 12Collatz_holds_for 12Collatz_holds_for (next_syracuse 12)Collatz_holds_for (if even 12 then div2 12 else 3 * 12 + 1)Collatz_holds_for 6Collatz_holds_for (next_syracuse 6)Collatz_holds_for (if even 6 then div2 6 else 3 * 6 + 1)Collatz_holds_for 3Collatz_holds_for (next_syracuse 3)Collatz_holds_for (if even 3 then div2 3 else 3 * 3 + 1)Collatz_holds_for 10Collatz_holds_for (next_syracuse 10)Collatz_holds_for (if even 10 then div2 10 else 3 * 10 + 1)Collatz_holds_for 5Collatz_holds_for (next_syracuse 5)Collatz_holds_for (if even 5 then div2 5 else 3 * 5 + 1)Collatz_holds_for 16Collatz_holds_for (next_syracuse 16)Collatz_holds_for (if even 16 then div2 16 else 3 * 16 + 1)Collatz_holds_for 8Collatz_holds_for (next_syracuse 8)Collatz_holds_for (if even 8 then div2 8 else 3 * 8 + 1)Collatz_holds_for 4Collatz_holds_for (next_syracuse 4)Collatz_holds_for (if even 4 then div2 4 else 3 * 4 + 1)Collatz_holds_for 2Collatz_holds_for (next_syracuse 2)Collatz_holds_for (if even 2 then div2 2 else 3 * 2 + 1)apply Chf_done. Qed.Collatz_holds_for 1
Of course if you manage to prove the theorem below, I would be very impressed.
forall n : nat, Collatz_holds_for nAdmitted.forall n : nat, Collatz_holds_for n
Taking A Step Back: Defining Inductive Propositions
You now have a methodology to define any kind of relations as inductive propositions. As we translated blackboard rules into Coq, you may have noted that the syntax was strangely similar to defining inductive datatypes. This is an important observation, that we'll reuse soon when doing proofs about inductive propositions.
If we think about the blackboard rules, whenever you use them to prove something, you build a "proof derivation" or a "proof tree". This proof tree itself looks like something we could describe with an inductive datatype! In some way, by defining the Coq inductive proposition, you defined the different ways to build the proof tree: it's as if you described the proof tree inductive type. Now, proving that some inductive proposition holds amounts to constructing a proof tree, or constructing an element of that inductive type you just defined.
In Coq, we can show the proofs object being constructed. Let's look at these proof trees for an example.
Collatz_holds_for 12Collatz_holds_for 12Collatz_holds_for 6Collatz_holds_for 6Collatz_holds_for 3Collatz_holds_for 3Collatz_holds_for 10Collatz_holds_for 10Collatz_holds_for 5Collatz_holds_for 5Collatz_holds_for 16Collatz_holds_for 16Collatz_holds_for 8Collatz_holds_for 8Collatz_holds_for 4Collatz_holds_for 4Collatz_holds_for 2Collatz_holds_for 2Collatz_holds_for 1apply Chf_done. Qed.Collatz_holds_for 1
Now, let's familiarize ourselves with some more inductive propositions. We can use them to represent sets!
Inductive my_favorite_numbers : nat -> Prop :=
| ILike17 : my_favorite_numbers 17
| ILike23 : my_favorite_numbers 23
| ILike28 : my_favorite_numbers 28.
Let's a pick a simple relation between natural numbers: being strictly less than another number.
Inductive lt: nat -> nat -> Prop := | lt_next: forall n, lt n (S n) | lt_skip: forall n m (LT: lt n m), lt n (S m).lt 3 6lt 3 6lt 3 5apply lt_next. Qed.lt 3 4
In fact, this lt
relation can be seen as the transitive closure of another relation, relating numbers that are off by one (for instance, 4 and 5).
We can similarly define this notion of transitive closure as an inductive proposition.
(* TC : Transitive Closure *) Inductive tc {A} (R : A -> A -> Prop) : A -> A -> Prop := | TcBase : forall x y, R x y -> tc R x y | TcTrans : forall x y z, tc R x y -> tc R y z -> tc R x z.
Let's redefine lt
now as a transitive closure.
Definition oneApart (n m : nat) : Prop := n + 1 = m. Definition lt' : nat -> nat -> Prop := tc oneApart.
Let's see another use of the transitive closure: parent relationships. Let's define a family with their parent relationships.
Inductive person : Type := | Hera | Ares | Hephaestus | Eros. Inductive parent_of: person -> person -> Prop := | he_ar: parent_of Hera Ares | he_he: parent_of Hera Hephaestus | ar_er: parent_of Ares Eros. Definition ancestor_of : person -> person -> Prop := tc parent_of.ancestor_of Hera Erosancestor_of Hera Erostc parent_of Hera Erostc parent_of Hera Arestc parent_of Ares Erostc parent_of Hera Aresapply he_ar.parent_of Hera Arestc parent_of Ares Erosapply ar_er. Qed.parent_of Ares Eros
Useless question: what do you think is the simplest way to define an inductive proposition that never holds? And an inductive relation that always hold?
Inductive useless: nat -> Prop := . Inductive total_relation : nat -> nat -> Prop := | tot n m : total_relation n m.
One last example: we can define what it means for natural numbers to be even. Of course there are many equivalent definitions.
Inductive ev : nat -> Prop :=
| ev_0: ev 0
| ev_SS (n : nat) (H : ev n) : ev (S (S n)).
Inductive Reasoning
By defining our inductive propositions with rules, we've not only a way to construct evidence that a property holds, using each rule as a theorem. We've also established that the only way for the property to hold is by having a proof tree using only these inductive constructors.
This is a very strong observation when conducting proofs with inductive propositions!
Defining ev
with an Inductive
declaration tells Coq not
only that the constructors ev_0
and ev_SS
are valid ways to
build evidence that some number is ev
, but also that these two
constructors are the only ways to build evidence that numbers
are ev
.
To go back to the inductive types comparison, similarly every list is either a nil list or the concatenation of some elements to some other list.
This means that, just like on inductive types, we can destruct and do induction on the proof trees of inductive propositions!
So we can for instance prove the following lemma!
forall n : nat, ev n -> n = 0 \/ (exists n' : nat, n = S (S n') /\ ev n')forall n : nat, ev n -> n = 0 \/ (exists n' : nat, n = S (S n') /\ ev n')n: nat
E: ev nn = 0 \/ (exists n' : nat, n = S (S n') /\ ev n')E: ev 0
EE: E = ev_00 = 0 \/ (exists n' : nat, 0 = S (S n') /\ ev n')n': nat
E: ev (S (S n'))
E': ev n'
EE: E = ev_SS n' E'S (S n') = 0 \/ (exists n'0 : nat, S (S n') = S (S n'0) /\ ev n'0)E: ev 0
EE: E = ev_00 = 0 \/ (exists n' : nat, 0 = S (S n') /\ ev n')reflexivity.E: ev 0
EE: E = ev_00 = 0n': nat
E: ev (S (S n'))
E': ev n'
EE: E = ev_SS n' E'S (S n') = 0 \/ (exists n'0 : nat, S (S n') = S (S n'0) /\ ev n'0)n': nat
E: ev (S (S n'))
E': ev n'
EE: E = ev_SS n' E'exists n'0 : nat, S (S n') = S (S n'0) /\ ev n'0n': nat
E: ev (S (S n'))
E': ev n'
EE: E = ev_SS n' E'S (S n') = S (S n') /\ ev n'n': nat
E: ev (S (S n'))
E': ev n'
EE: E = ev_SS n' E'S (S n') = S (S n')n': nat
E: ev (S (S n'))
E': ev n'
EE: E = ev_SS n' E'ev n'apply E'. Qed.n': nat
E: ev (S (S n'))
E': ev n'
EE: E = ev_SS n' E'ev n'
Facts like this are often called "inversion lemmas" because they allow us to "invert" some given information to reason about all the different ways it could have been derived.
Here, there are two ways to prove ev n
, and the inversion lemma
makes this explicit.
We can use the inversion lemma that we proved above to help structure proofs:
forall n : nat, ev (S (S n)) -> ev nforall n : nat, ev (S (S n)) -> ev nn: nat
H: ev (S (S n))ev nn: nat
H: S (S n) = 0 \/ (exists n' : nat, S (S n) = S (S n') /\ ev n')ev nn: nat
H0: S (S n) = 0ev nn: nat
H1: exists n' : nat, S (S n) = S (S n') /\ ev n'ev ndiscriminate H0.n: nat
H0: S (S n) = 0ev nn: nat
H1: exists n' : nat, S (S n) = S (S n') /\ ev n'ev nn, n': nat
Hnm: S (S n) = S (S n')
Hev: ev n'ev nn, n': nat
Heq: n = n'
Hev: ev n'ev napply Hev. Qed.n, n': nat
Heq: n = n'
Hev: ev n'ev n'
But this such a common reasoning pattern that
Coq provides a handy tactic called inversion
that factors it out, saving us the trouble of explicitly stating
and proving an inversion lemma for every Inductive
definition we
make.
Here, the inversion
tactic can detect (1) that the first case,
where n = 0
, does not apply and (2) that the n'
that appears
in the ev_SS
case must be the same as n
. It includes an
as
annotation similar to destruct
, allowing us to assign
names rather than have Coq choose them.
forall n : nat, ev (S (S n)) -> ev nforall n : nat, ev (S (S n)) -> ev nn: nat
E: ev (S (S n))ev n(* We are in the [E = ev_SS n' E'] case now. *) apply E'. Qed.n: nat
E: ev (S (S n))
n': nat
E': ev n
Heq: n' = nev n
Some more examples proofs with inversion:
~ ev 1~ ev 1inversion H. Qed.H: ev 1Falseforall n : nat, my_favorite_numbers n -> n < 50forall n : nat, my_favorite_numbers n -> n < 50n: nat
H: my_favorite_numbers nn < 50n: nat
H: my_favorite_numbers n
H0: 17 = n17 < 50n: nat
H: my_favorite_numbers n
H0: 23 = n23 < 50n: nat
H: my_favorite_numbers n
H0: 28 = n28 < 50lia.n: nat
H: my_favorite_numbers n
H0: 17 = n17 < 50lia.n: nat
H: my_favorite_numbers n
H0: 23 = n23 < 50lia. Qed.n: nat
H: my_favorite_numbers n
H0: 28 = n28 < 50forall n : nat, ev (S (S (S (S n)))) -> ev nforall n : nat, ev (S (S (S (S n)))) -> ev nn: nat
E: ev (S (S (S (S n))))ev nn: nat
E: ev (S (S (S (S n))))
n': nat
E': ev (S (S n))
EQ': n' = S (S n)ev napply E''. Qed.n: nat
E: ev (S (S (S (S n))))
n': nat
E': ev (S (S n))
EQ': n' = S (S n)
n'': nat
E'': ev n
EQ'': n'' = nev n
The inversion
tactic does quite a bit of work. For
example, when applied to an equality assumption, it does the work
of both discriminate
and injection
. In addition, it carries
out the intros
and rewrite`s that are typically necessary in
the case of `injection
. It can also be applied to analyze
evidence for arbitrary inductively defined propositions, not just
equality.
The tactic inversion
actually works on any H : P
where P
is defined inductively:
For each constructor of
P
, make a subgoal whereH
is constrained by the form of this constructor.Discard contradictory subgoals (such as
ev_0
above).Generate auxiliary equalities (as with
ev_SS
above).
But let's go even further. Just like we've seen that we can define a principle of induction on any inductive datatype, without surprise we can do inductive reasoning on the proof trees of inductive propositions!
The behavior of induction
on evidence (or proof tree) is the same as its
behavior on data: It causes Coq to generate one subgoal for each
constructor that could have used to build that evidence, while
providing an induction hypothesis for each recursive occurrence of
the property in question.
Let's revisit our two definitions of eval
and their equivalence;
forall (vn : val) (e : arith) (n : nat), ieval vn e n -> eval vn e = nforall (vn : val) (e : arith) (n : nat), ieval vn e n -> eval vn e = nvn: val
e: arithforall n : nat, ieval vn e n -> eval vn e = nvn: val
e1, e2: arith
IHe1: forall n : nat, ieval vn e1 n -> eval vn e1 = n
IHe2: forall n : nat, ieval vn e2 n -> eval vn e2 = n
n1, n2: nat
H: ieval vn (Plus e1 e2) (n1 + n2)
EVAL_LEFT: ieval vn e1 n1
EVAL_RIGHT: ieval vn e2 n2eval vn e1 + eval vn e2 = n1 + n2vn: val
e1, e2: arith
IHe1: forall n : nat, ieval vn e1 n -> eval vn e1 = n
IHe2: forall n : nat, ieval vn e2 n -> eval vn e2 = n
n1, n2: nat
H: ieval vn (Times e1 e2) (n1 * n2)
EVAL_LEFT: ieval vn e1 n1
EVAL_RIGHT: ieval vn e2 n2eval vn e1 * eval vn e2 = n1 * n2vn: val
e1, e2: arith
IHe1: forall n : nat, ieval vn e1 n -> eval vn e1 = n
IHe2: forall n : nat, ieval vn e2 n -> eval vn e2 = n
n1, n2: nat
H: ieval vn (Plus e1 e2) (n1 + n2)
EVAL_LEFT: ieval vn e1 n1
EVAL_RIGHT: ieval vn e2 n2eval vn e1 + eval vn e2 = n1 + n2vn: val
e1, e2: arith
IHe1: forall n : nat, ieval vn e1 n -> eval vn e1 = n
IHe2: forall n : nat, ieval vn e2 n -> eval vn e2 = n
n1, n2: nat
H: ieval vn (Plus e1 e2) (n1 + n2)
EVAL_LEFT: eval vn e1 = n1
EVAL_RIGHT: ieval vn e2 n2eval vn e1 + eval vn e2 = n1 + n2vn: val
e1, e2: arith
IHe1: forall n : nat, ieval vn e1 n -> eval vn e1 = n
IHe2: forall n : nat, ieval vn e2 n -> eval vn e2 = n
n1, n2: nat
H: ieval vn (Plus e1 e2) (n1 + n2)
EVAL_LEFT: eval vn e1 = n1
EVAL_RIGHT: eval vn e2 = n2eval vn e1 + eval vn e2 = n1 + n2vn: val
e1, e2: arith
IHe1: forall n : nat, ieval vn e1 n -> eval vn e1 = n
IHe2: forall n : nat, ieval vn e2 n -> eval vn e2 = n
n1, n2: nat
H: ieval vn (Plus e1 e2) (n1 + n2)
EVAL_LEFT: eval vn e1 = n1
EVAL_RIGHT: eval vn e2 = n2n1 + eval vn e2 = n1 + n2reflexivity.vn: val
e1, e2: arith
IHe1: forall n : nat, ieval vn e1 n -> eval vn e1 = n
IHe2: forall n : nat, ieval vn e2 n -> eval vn e2 = n
n1, n2: nat
H: ieval vn (Plus e1 e2) (n1 + n2)
EVAL_LEFT: eval vn e1 = n1
EVAL_RIGHT: eval vn e2 = n2n1 + n2 = n1 + n2vn: val
e1, e2: arith
IHe1: forall n : nat, ieval vn e1 n -> eval vn e1 = n
IHe2: forall n : nat, ieval vn e2 n -> eval vn e2 = n
n1, n2: nat
H: ieval vn (Times e1 e2) (n1 * n2)
EVAL_LEFT: ieval vn e1 n1
EVAL_RIGHT: ieval vn e2 n2eval vn e1 * eval vn e2 = n1 * n2vn: val
e1, e2: arith
IHe1: forall n : nat, ieval vn e1 n -> eval vn e1 = n
IHe2: forall n : nat, ieval vn e2 n -> eval vn e2 = n
n1, n2: nat
H: ieval vn (Times e1 e2) (n1 * n2)
EVAL_LEFT: eval vn e1 = n1
EVAL_RIGHT: ieval vn e2 n2eval vn e1 * eval vn e2 = n1 * n2vn: val
e1, e2: arith
IHe1: forall n : nat, ieval vn e1 n -> eval vn e1 = n
IHe2: forall n : nat, ieval vn e2 n -> eval vn e2 = n
n1, n2: nat
H: ieval vn (Times e1 e2) (n1 * n2)
EVAL_LEFT: eval vn e1 = n1
EVAL_RIGHT: eval vn e2 = n2eval vn e1 * eval vn e2 = n1 * n2vn: val
e1, e2: arith
IHe1: forall n : nat, ieval vn e1 n -> eval vn e1 = n
IHe2: forall n : nat, ieval vn e2 n -> eval vn e2 = n
n1, n2: nat
H: ieval vn (Times e1 e2) (n1 * n2)
EVAL_LEFT: eval vn e1 = n1
EVAL_RIGHT: eval vn e2 = n2n1 * eval vn e2 = n1 * n2reflexivity. Qed.vn: val
e1, e2: arith
IHe1: forall n : nat, ieval vn e1 n -> eval vn e1 = n
IHe2: forall n : nat, ieval vn e2 n -> eval vn e2 = n
n1, n2: nat
H: ieval vn (Times e1 e2) (n1 * n2)
EVAL_LEFT: eval vn e1 = n1
EVAL_RIGHT: eval vn e2 = n2n1 * n2 = n1 * n2
Now with an induction on ieval
:
forall (vn : val) (e : arith) (n : nat), ieval vn e n -> eval vn e = nforall (vn : val) (e : arith) (n : nat), ieval vn e n -> eval vn e = nvn: val
e: arith
n: nat
H: ieval vn e neval vn e = nvn: val
n: natn = nvn: val
v: var
n: nat
EVAL_VAR: vn v = nvn v = nvn: val
e1, e2: arith
n1, n2: nat
H: ieval vn e1 n1
H0: ieval vn e2 n2
IHieval1: eval vn e1 = n1
IHieval2: eval vn e2 = n2eval vn e1 + eval vn e2 = n1 + n2vn: val
e1, e2: arith
n1, n2: nat
H: ieval vn e1 n1
H0: ieval vn e2 n2
IHieval1: eval vn e1 = n1
IHieval2: eval vn e2 = n2eval vn e1 * eval vn e2 = n1 * n2reflexivity.vn: val
n: natn = nassumption.vn: val
v: var
n: nat
EVAL_VAR: vn v = nvn v = nvn: val
e1, e2: arith
n1, n2: nat
H: ieval vn e1 n1
H0: ieval vn e2 n2
IHieval1: eval vn e1 = n1
IHieval2: eval vn e2 = n2eval vn e1 + eval vn e2 = n1 + n2vn: val
e1, e2: arith
n1, n2: nat
H: ieval vn e1 n1
H0: ieval vn e2 n2
IHieval1: eval vn e1 = n1
IHieval2: eval vn e2 = n2n1 + eval vn e2 = n1 + n2reflexivity.vn: val
e1, e2: arith
n1, n2: nat
H: ieval vn e1 n1
H0: ieval vn e2 n2
IHieval1: eval vn e1 = n1
IHieval2: eval vn e2 = n2n1 + n2 = n1 + n2vn: val
e1, e2: arith
n1, n2: nat
H: ieval vn e1 n1
H0: ieval vn e2 n2
IHieval1: eval vn e1 = n1
IHieval2: eval vn e2 = n2eval vn e1 * eval vn e2 = n1 * n2vn: val
e1, e2: arith
n1, n2: nat
H: ieval vn e1 n1
H0: ieval vn e2 n2
IHieval1: eval vn e1 = n1
IHieval2: eval vn e2 = n2n1 * eval vn e2 = n1 * n2reflexivity. Qed. (* a bit simpler: *)vn: val
e1, e2: arith
n1, n2: nat
H: ieval vn e1 n1
H0: ieval vn e2 n2
IHieval1: eval vn e1 = n1
IHieval2: eval vn e2 = n2n1 * n2 = n1 * n2forall (vn : val) (e : arith) (n : nat), ieval vn e n -> eval vn e = ninduction 1; simpl; auto. Qed.forall (vn : val) (e : arith) (n : nat), ieval vn e n -> eval vn e = n
Even though we got to the same result by doing either an induction on an element of an inductive type or an induction on the inductive proposition, it's important to understand that we are doing two entirely different proofs.
Let's see an example with ev
.
forall n m : nat, ev (n + m) -> ev n -> ev mforall n m : nat, ev (n + m) -> ev n -> ev mm: nat
H: ev (0 + m)
H0: ev 0ev mn: nat
IHn: forall m : nat, ev (n + m) -> ev n -> ev m
m: nat
H: ev (S n + m)
H0: ev (S n)ev mm: nat
H: ev (0 + m)
H0: ev 0ev massumption.m: nat
H: ev m
H0: ev 0ev mn: nat
IHn: forall m : nat, ev (n + m) -> ev n -> ev m
m: nat
H: ev (S n + m)
H0: ev (S n)ev mn: nat
IHn: forall m : nat, ev (n + m) -> ev n -> ev m
m: nat
H: ev (S n + m)
H0: ev (S n)ev (n + m)n: nat
IHn: forall m : nat, ev (n + m) -> ev n -> ev m
m: nat
H: ev (S n + m)
H0: ev (S n)ev n
This induction hypothesis does not seem helpful at all.
Abort.forall n m : nat, ev (n + m) -> ev n -> ev mforall n m : nat, ev (n + m) -> ev n -> ev mn, m: natev (n + m) -> ev n -> ev mm: natforall n : nat, ev (n + m) -> ev n -> ev mn: nat
H: ev (n + 0)
H0: ev nev 0m: nat
IHm: forall n : nat, ev (n + m) -> ev n -> ev m
n: nat
H: ev (n + S m)
H0: ev nev (S m)constructor.n: nat
H: ev (n + 0)
H0: ev nev 0m: nat
IHm: forall n : nat, ev (n + m) -> ev n -> ev m
n: nat
H: ev (n + S m)
H0: ev nev (S m)
I don't have anything in my context to prove ev (S m)
…
Abort. (* OK I get it, you want me to use induction on a prop: *)forall n m : nat, ev (n + m) -> ev n -> ev mforall n m : nat, ev (n + m) -> ev n -> ev mn, m: nat
Hnm: ev (n + m)ev n -> ev m(* it seems like we lose the information that n+m is 0... *)n, m: nat
Hnm: ev (n + m)ev n -> ev m(* otherwise it gets discarded *)n, m, nm: nat
Heqnm: nm = n + m
Hnm: ev nmev n -> ev mn, m: nat
Heqnm: 0 = n + m
Hn: ev nev mn, m, n0: nat
Heqnm: S (S n0) = n + m
Hnm: ev n0
IHHnm: n0 = n + m -> ev n -> ev m
Hn: ev nev mn, m: nat
Heqnm: 0 = n + m
Hn: ev nev mn: nat
Heqnm: 0 = n + 0
Hn: ev nev 0n, m: nat
Heqnm: 0 = n + S m
Hn: ev nev (S m)constructor.n: nat
Heqnm: 0 = n + 0
Hn: ev nev 0n, m: nat
Heqnm: 0 = n + S m
Hn: ev nev (S m)inversion Heqnm.n, m: nat
Heqnm: 0 = S (n + m)
Hn: ev nev (S m)n, m, n0: nat
Heqnm: S (S n0) = n + m
Hnm: ev n0
IHHnm: n0 = n + m -> ev n -> ev m
Hn: ev nev mn, m, n0: nat
Heqnm: S (S n0) = n + m
Hnm: ev n0
IHHnm: n0 = n + m -> ev n -> ev m
Hn: ev nn0 = n + mn, m, n0: nat
Heqnm: S (S n0) = n + m
Hnm: ev n0
IHHnm: n0 = n + m -> ev n -> ev m
Hn: ev nev n
I'm still lost.
Abort.
Actually, not only do we have to be careful about induction on a value vs induction on a proposition but also, induction on different propositions are different proofs: choose the right one!
forall n m : nat, ev (n + m) -> ev n -> ev mforall n m : nat, ev (n + m) -> ev n -> ev mn, m: nat
Enm: ev (n + m)
En: ev nev mm: nat
Enm: ev (0 + m)ev mm, n': nat
Enm: ev (S (S n') + m)
En': ev n'
IHEn': ev (n' + m) -> ev mev mm: nat
Enm: ev (0 + m)ev mapply Enm.m: nat
Enm: ev mev mm, n': nat
Enm: ev (S (S n') + m)
En': ev n'
IHEn': ev (n' + m) -> ev mev mm, n': nat
Enm: ev (S (S (n' + m)))
En': ev n'
IHEn': ev (n' + m) -> ev mev mm, n': nat
Enm: ev (S (S (n' + m)))
En': ev n'
IHEn': ev (n' + m) -> ev m
n: nat
H0: ev (n' + m)
H: n = n' + mev mapply H0. Qed.m, n': nat
Enm: ev (S (S (n' + m)))
En': ev n'
IHEn': ev (n' + m) -> ev m
n: nat
H0: ev (n' + m)
H: n = n' + mev (n' + m)
Maybe there's a lesson here... Doing induction on random things may not be the best proof strategy.
Every time we define an inductive proposition, Coq creates an induction principle:
(* just like for inductive types: *)(* for inductive propositions: *)