(* lecture 2/17/2014 *) (** * Induction: Proof by Induction *) (** The next line imports all of our definitions from the previous chapter. *) Require Export Basics. (** For it to work, you need to use [coqc] to compile [Basics.v] into [Basics.vo]. (This is like making a .class file from a .java file, or a .o file from a .c file.) Here are two ways to compile your code: - CoqIDE: Open [Basics.v]. In the "Compile" menu, click on "Compile Buffer". - Command line: Run [coqc Basics.v] *) (* ###################################################################### *) (** * Naming Cases *) (** The fact that there is no explicit command for moving from one branch of a case analysis to the next can make proof scripts rather hard to read. In larger proofs, with nested case analyses, it can even become hard to stay oriented when you're sitting with Coq and stepping through the proof. (Imagine trying to remember that the first five subgoals belong to the inner case analysis and the remaining seven cases are what remains of the outer one...) Disciplined use of indentation and comments can help, but a better way is to use the [Case] tactic. *) (* [Case] is not built into Coq: we need to define it ourselves. There is no need to understand how it works -- you can just skip over the definition to the example that follows. It uses some facilities of Coq that we have not discussed -- the string library (just for the concrete syntax of quoted strings) and the [Ltac] command, which allows us to declare custom tactics. Kudos to Aaron Bohannon for this nice hack! *) Require String. Open Scope string_scope. Ltac move_to_top x := match reverse goal with | H : _ |- _ => try move x after H end. Tactic Notation "assert_eq" ident(x) constr(v) := let H := fresh in assert (x = v) as H by reflexivity; clear H. Tactic Notation "Case_aux" ident(x) constr(name) := first [ set (x := name); move_to_top x | assert_eq x name; move_to_top x | fail 1 "because we are working on a different case" ]. Tactic Notation "Case" constr(name) := Case_aux Case name. Tactic Notation "SCase" constr(name) := Case_aux SCase name. Tactic Notation "SSCase" constr(name) := Case_aux SSCase name. Tactic Notation "SSSCase" constr(name) := Case_aux SSSCase name. Tactic Notation "SSSSCase" constr(name) := Case_aux SSSSCase name. Tactic Notation "SSSSSCase" constr(name) := Case_aux SSSSSCase name. Tactic Notation "SSSSSSCase" constr(name) := Case_aux SSSSSSCase name. Tactic Notation "SSSSSSSCase" constr(name) := Case_aux SSSSSSSCase name. (** Here's an example of how [Case] is used. Step through the following proof and observe how the context changes. *) Theorem andb_true_elim1 : forall b c : bool, andb b c = true -> b = true. Proof. intros b c H. destruct b. Case "b = true". (* <----- here *) reflexivity. Case "b = false". (* <---- and here *) rewrite <- H. reflexivity. Qed. (** [Case] does something very straightforward: It simply adds a string that we choose (tagged with the identifier "Case") to the context for the current goal. When subgoals are generated, this string is carried over into their contexts. When the last of these subgoals is finally proved and the next top-level goal becomes active, this string will no longer appear in the context and we will be able to see that the case where we introduced it is complete. Also, as a sanity check, if we try to execute a new [Case] tactic while the string left by the previous one is still in the context, we get a nice clear error message. For nested case analyses (e.g., when we want to use a [destruct] to solve a goal that has itself been generated by a [destruct]), there is an [SCase] ("subcase") tactic. *) (** **** Exercise: 2 stars (andb_true_elim2) *) (** Prove [andb_true_elim2], marking cases (and subcases) when you use [destruct]. *) Theorem andb_true_elim2 : forall b c : bool, andb b c = true -> c = true. (* Proof. (* FILL IN HERE *) Admitted. (** [] *) *) Proof. intros b c H. destruct c. reflexivity. rewrite <- H. destruct b. reflexivity. reflexivity. Qed.