IndPrinciplesInduction Principles

With the Curry-Howard correspondence and its realization in Coq in mind, we can now take a deeper look at induction principles.

Proof by Recursion

In the previous chapter we saw most of our favorite tactics explained as simple programs:
  • destruct is just case analysis (match)
  • apply is function application
  • rewrite is an application of eq_eqL
  • split, left, right, and reflexivity are just applying constructors
But what about induction?
Let's start by writing out a suitable inductive predicate. A nice option is the Coq standard library's definition of even and odd.

Inductive even : nat Prop :=
| eO : even 0
| eS : n, odd n even (S n)

with odd : nat Prop :=
| oS : n, even n odd (S n).
We just wrote a "mutually inductive" definition. In short, it says that O is Even, the successor of an Odd number is Even and then successor of an Even number is Odd.
Here's the lemma we'd like to prove.

Lemma even_or_odd : (n : nat), even n odd n.
Abort.
The proof of even_or_odd is actually quite easy to program!

Fixpoint even_or_odd (n : nat) : even n odd n
  (* WORKED IN CLASS *) :=
  match n with
  | 0 ⇒ or_introl eO
  | S n'match even_or_odd n' with
           | or_introl eor_intror (oS n' e)
           | or_intror oor_introl (eS n' o)
           end
  end.
Unfortunately, writing entire proofs in the programming language can be quite hard. Fortunately, there is a simple program that makes these proofs easy.

Check nat_ind.
nat_ind takes four arguments:
    1) A proposition P over natural numbers
    2) A proof of P O 
    3) A proof that P m -> P (S m). 
    4) An arbitrary n
and it constructs a recursive proof that P n.
Let's try to prove nat_ind ourselves.

Fixpoint nat_ind (P : nat Prop)
                 (p0 : P 0)
                 (pS : m, P m P (S m))
                 (n : nat)
                 {struct n}
                 : P n
  (* WORKED IN CLASS *) :=
  match n with
  | 0 ⇒ p0
  | S n'pS n' (nat_ind P p0 pS n')
  end.
We can now use nat_ind to prove even_or_odd in the proof environment

Lemma even_or_odd' (n : nat) : even n odd n.
Proof.
  apply nat_ind with (n := n).
  - apply or_introl.
    apply eO.
  - intros n' IHn.
    destruct IHn as [He | Ho].
    + apply or_intror. apply oS. apply He.
    + apply or_introl. apply eS. apply Ho.
Defined.

Aside: Proving False

Now that we know how to write recursive proofs, let's do something fun.
Let's prove False.


Fixpoint true_then_false (t : True) : False.
(* WORKED IN CLASS *)
  apply true_then_false. apply t.
Admitted.

Definition proof_of_false : False
  (* WORKED IN CLASS *)
  := true_then_false I.

Induction in Depth

Every time we declare a new Inductive datatype, Coq automatically generates an induction principle for this type. This induction principle is a theorem like any other: If t is defined inductively, the corresponding induction principle is called t_ind. Here is the one for lists:

Check list_ind.
(*  ===> list_ind
     : forall (X : Type) (P : list X -> Prop),
       P   ->
       (forall (x : X) (l : list X), P l -> P (x :: l)) -> 
       forall l : list X, P l *)

The induction tactic is a straightforward wrapper that, at its core, simply performs apply t_ind. To see this more clearly, let's experiment with directly using apply nat_ind, instead of the induction tactic, to carry out some proofs. Here, for example, is an alternate proof of a theorem that we saw in the Induction chapter.

Theorem app_0_r' : {X : Type} (l : list X),
  l ++ [] = l.
Proof.
  intros X.
  apply list_ind.
  - (* l =  *) reflexivity.
  - (* l = x :: l' *) simpl. intros x l' IH. rewriteIH.
    reflexivity. Qed.
This proof is basically the same as the earlier one, but a few minor differences are worth noting.
First, in the induction step of the proof (the "x :: l" case), we have to do a little bookkeeping manually (the intros) that induction does automatically.
Second, we do not introduce l into the context before applying list_ind -- the conclusion of list_ind is a quantified formula, and apply needs this conclusion to exactly match the shape of the goal state, including the quantifier. (However, we can specify l using with.) By contrast, the induction tactic works either with a variable in the context or a quantified variable in the goal.
Third, we had to manually supply the name of the induction principle with apply, but induction figures that out itself.
These conveniences make induction nicer to use in practice than applying induction principles like nat_ind directly. But it is important to realize that, modulo these bits of bookkeeping, applying list_ind is what we are really doing.

Exercise: 2 stars, standard, optional (plus_one_r')

Complete this proof without using the induction tactic.

Theorem len_app' : {X : Type} (l1 l2 : list X),
  length (l1 ++ l2) = length l1 + length l2.
Proof.
  (* FILL IN HERE *) Admitted.
Coq generates induction principles for every datatype defined with Inductive, including those that aren't recursive. Although of course we don't need induction to prove properties of non-recursive datatypes, the idea of an induction principle still makes sense for them: it gives a way to prove that a property holds for all values of the type.
These generated principles follow a similar pattern. If we define a type t with constructors c1 ... cn, Coq generates a theorem with this shape:
    t_ind : P : tProp,
              ... case for c1 ... →
              ... case for c2 ... → ...
              ... case for cn ... →
               n : t, P n
The specific shape of each case depends on the arguments to the corresponding constructor.
Before trying to write down a general rule, let's look at some more examples. First, an example where the constructors take no arguments:

Inductive time : Type :=
  | day
  | night.

Check time_ind.
(* ===> time_ind : forall P : time -> Prop,
                          P day ->
                          P night ->
                          forall t : time, P t *)

Exercise: 1 star, standard, optional (rgb)

Write out the induction principle that Coq will generate for the following datatype. Write down your answer on paper or type it into a comment, and then compare it with what Coq prints.

Inductive rgb : Type :=
  | red
  | green
  | blue.
Check rgb_ind.
In general, the automatically generated induction principle for inductive type t is formed as follows:
  • Each constructor c generates one case of the principle.
  • If c takes no arguments, that case is:
          "P holds of c"
  • If c takes arguments x1:a1 ... xn:an, that case is:
          "For all x1:a1 ... xn:an, if [P] holds of each of the arguments of type [t], then [P] holds of [c x1 ... xn]"

Exercise: 1 star, standard, optional (booltree_ind)

Write out the induction principle that Coq will generate for the following datatype. (Again, write down your answer on paper or type it into a comment, and then compare it with what Coq prints.)

Inductive booltree : Type :=
 | bt_empty
 | bt_leaf (b : bool)
 | bt_branch (b : bool) (t1 t2 : booltree).

Exercise: 1 star, standard, optional (ex_set)

Here is an induction principle for an inductively defined set.
      ExSet_ind :
          P : ExSetProp,
             ( b : bool, P (con1 b)) →
             ( (n : nat) (e : ExSet), P eP (con2 n e)) →
              e : ExSet, P e
Give an Inductive definition of ExSet:

Inductive ExSet : Type :=
  (* FILL IN HERE *)
.

Induction Principles in Prop

Earlier, we looked in detail at the induction principles that Coq generates for inductively defined sets. The induction principles for inductively defined propositions like even are a tiny bit more complicated. As with all induction principles, we want to use the induction principle on even to prove things by inductively considering the possible shapes that something in even can have. Intuitively speaking, however, what we want to prove are not statements about evidence but statements about numbers: accordingly, we want an induction principle that lets us prove properties of numbers by induction on evidence. For example:
From what we've said so far, you might expect the inductive definition of ev...
      Inductive ev : natProp :=
      | ev_0 : even 0
      | ev_SS : n : nat, even neven (S (S n)).
...to give rise to an induction principle that looks like this...
    ev_ind_max : P : ( n : nat, even nProp),
         P O ev_0
         ( (m : nat) (E : ev m),
            P m E
            P (S (S m)) (ev_SS m E)) →
          (n : nat) (E : ev n),
         P n E
... because:
  • Since ev is indexed by a number n (every ev object E is a piece of evidence that some particular number n is even), the proposition P is parameterized by both n and E -- that is, the induction principle can be used to prove assertions involving both an even number and the evidence that it is even.
  • Since there are two ways of giving evidence of evenness (even has two constructors), applying the induction principle generates two subgoals:
    • We must prove that P holds for O and ev_0.
    • We must prove that, whenever m is an even number and E is an evidence of its evenness, if P holds of m and E, then it also holds of S (S m) and ev_SS m E.
  • If these subgoals can be proved, then the induction principle tells us that P is true for all even numbers n and evidence E of their evenness.
This is more flexibility than we normally need or want: it is giving us a way to prove logical assertions where the assertion involves properties of some piece of evidence of evenness, while all we really care about is proving properties of numbers that are even -- we are interested in assertions about numbers, not about evidence. It would therefore be more convenient to have an induction principle for proving propositions P that are parameterized just by n and whose conclusion establishes P for all even numbers n:
        P : natProp,
         ... →
        n : nat,
         even nP n
For this reason, Coq actually generates the following simplified induction principle for ev:

Print ev.
(* ===>
Inductive ev : nat -> Prop :=
  | ev_0 : ev 0
  | ev_SS : forall n : nat, ev n -> ev (S (S n))
*)


Check ev_ind.
(* ===> ev_ind
        : forall P : nat -> Prop,
          P 0 ->
          (forall n : nat, ev n -> P n -> P (S (S n))) ->
          forall n : nat,
          ev n -> P n *)

In particular, Coq has dropped the evidence term E as a parameter of the the proposition P.
In English, ev_ind says: Suppose P is a property of natural numbers (that is, P n is a Prop for every n). To show that P n holds whenever n is even, it suffices to show:
  • P holds for 0,
  • for any n, if n is even and P holds for n, then P holds for S (S n).
As expected, we can apply ev_ind directly instead of using induction. For example, we can use it to show that ev' (the slightly awkward alternate definition of evenness that we saw in an exercise in the \chap{IndProp} chapter) is equivalent to the cleaner inductive definition ev:

Inductive ev' : nat Prop :=
| ev'_0 : ev' 0
| ev'_2 : ev' 2
| ev'_sum n m (Hn : ev' n) (Hm : ev' m) : ev' (n + m).

Theorem ev_ev' : n, ev n ev' n.
Proof.
  apply ev_ind.
  - (* ev_0 *)
    apply ev'_0.
  - (* ev_SS *)
    intros m Hm IH.
    apply (ev'_sum 2 m).
    + apply ev'_2.
    + apply IH.
Qed.

Strengthening Induction Principles

Sometimes the default kind of induction that Coq gives you isn't sufficient for a given task. Let's try to prove a simple theorem about our definition of even from the beginning of this chapter.

(* Try 1 *)

Lemma even_divides_two_try1 : n, even n m, n = m × 2.
Proof.
  (* WORKED IN CLASS *)
  induction n; intros.
  - 0; reflexivity.
  -
Abort.
Maybe we can do induction on the even n predicate?

Lemma even_divides_two_try2 : n, even n m, n = m × 2.
Proof.
  intros n e.
  induction e.
  - 0; reflexivity.
  - inversion H; subst.
Abort.
We're going to need a better induction principle.

Fixpoint even_ind'
         (P : nat Prop)
         (p0 : P 0)
         (pSS : m, even m P m P (S (S m)))
         (n : nat)
         (e : even n)
         {struct e}
  : P n.
  refine (match e with
          | eOp0
          | eS n' pomatch po with
                       | oS n'' e'_
                       end
          end).
  apply pSS.
  apply e'.
  apply even_ind'; auto.
Defined.

Lemma even_divides_two : n, even n m, n = m × 2.
Proof.
(* WORKED IN CLASS *)
  intros.
  induction H as [|n' E] using even_ind'.
  - 0. reflexivity.
  - destruct IHE as [m' IHE].
     (S m').
    rewrite IHE.
    simpl.
    reflexivity.
Qed.

Exercise: 3 stars, standard, recommended (odd_decomposition)

Write a corresponding induction principle for odd and use it to prove the corresponding lemma about odd numbers.

(* Do NOT use the preceding lemma in your proof. *)

(* FILL IN HERE *)

Lemma odd_decomposition : n, odd n m, n = m × 2 + 1.
Proof.
(* FILL IN HERE *) Admitted.
Let's look at a more interesting example: an arbitrarily branching tree:

Require Import List.
Import ListNotations.

Inductive Tree :=
| Leaf : nat Tree
| Node : list Tree Tree.

Check Tree_ind.
(*
Tree_ind
     : forall P : Tree -> Prop,
       (forall n : nat, P (Leaf n)) -> 
       (forall l : list Tree, P (Node l)) -> 
     forall t : Tree, P t
 *)

Let's see if we can use this to prove the correctness of a tree mapping function:

Fixpoint map_Tree (f : nat nat) (t : Tree) {struct t} : Tree :=
  match t with
  | Leaf nLeaf (f n)
  | Node lNode (map (map_Tree f) l)
  end.

Inductive In_Tree : nat Tree Prop :=
| In_Tree_Leaf : n, In_Tree n (Leaf n)
| In_Tree_Node : n t l, In_Tree n t In t l In_Tree n (Node l).

Theorem map_correct :
   t m f, In_Tree m t In_Tree (f m) (map_Tree f t).
Proof.
  intros t.
  induction t.
  - intros m f HIn. simpl.
    inversion HIn; subst.
    constructor.
  - intros m f HIn. simpl.
    inversion HIn; subst.
    (* Now what? *)
Abort.

Fixpoint Tree_ind'
         (P : Tree Prop)
         (f : n, P (Leaf n))
         (g : l, ( x, In x l P x) P (Node l))
         (t : Tree)
         {struct t} : P t.
  refine (match t with
          | Leaf nf n
          | Node lg l _
          end).
  induction l.
  + intros t' H; inversion H.
  + intros t' H.
    destruct H.
    rewrite <- H.
    apply Tree_ind'; auto.
    apply IHl; auto.
Defined.

Theorem map_correct :
   t m f, In_Tree m t In_Tree (f m) (map_Tree f t).
Proof.
  intros t.
  induction t using Tree_ind'.
  - intros m f HIn. simpl.
    inversion HIn; subst.
    constructor.
  - intros m f HIn. simpl.
    inversion HIn; subst.
    econstructor.
    + apply H; eauto.
    + apply in_map.
      assumption.
Qed.

(* Thu Oct 24 15:26:21 EDT 2019 *)