(** * Software Foundations, Formally Benjamin C. Pierce Version of 10/17/2007 Before handing in this file with your homework solutions, please fill in the names of all members of your group: FILL IN HERE Also, please tell us roughly how many person-hours you spent on this assignment (i.e., if you worked in a group, give us the SUM of the number of hours spent by each person individually). FILL IN HERE *) Require Export lec10. (* ====================================================================== *) (* LECTURE 11 *) (* In this lecture we'll continue with a few more results about the extremely simple programming language that we've been working with for the last couple of lectures and then move on to a slightly larger and more interesting language... *) (* --------------------------------------------------------------------- *) (** * Multi-step evaluation *) Module SimpleArithEvalContd'. Export SimpleArithEvalContd. (* Until now, we've been working with the SINGLE-STEP EVALUATION RELATION [eval], which formalizes the individual steps of ABSTRACT MACHINE for executing programs. It is also interesting to use this machine to execute programs to completion, to find out what final result they yield. This can be formalized in two steps. First, we define a MULTI-STEP EVALUATION relation [evalmany], which relates terms [t] and [t'] if [t] can reach [t'] by any number (including zero) of single steps of evaluation. *) Notation evalmany := (refl_trans_closure _ eval). (* Note that we use Notation instead of Definition here. This means that [evalmany] will be automatically unfolded by Coq, which will simplify some of the proof automation later on. *) (* A few examples... *) Lemma check_evalmany_1: evalmany (tm_plus (tm_plus (tm_const zero) (tm_const three)) (tm_plus (tm_const two) (tm_const four))) (tm_const (plus (plus zero three) (plus two four))). Proof. apply rtc_trans with (y := tm_plus (tm_const (plus zero three)) (tm_plus (tm_const two) (tm_const four))). apply rtc_R. apply E_Plus1. apply E_PlusConstConst. apply rtc_trans with (y := tm_plus (tm_const (plus zero three)) (tm_const (plus two four))). apply rtc_R. apply E_Plus2. apply v_const. apply E_PlusConstConst. apply rtc_R. apply E_PlusConstConst. Qed. Lemma check_evalmany_2: evalmany (tm_const three) (tm_const three). Proof. (* FILL IN HERE (and delete "Admitted") *) Admitted. Lemma check_evalmany_3: evalmany (tm_plus (tm_const zero) (tm_const three)) (tm_plus (tm_const zero) (tm_const three)). Proof. (* FILL IN HERE (and delete "Admitted") *) Admitted. Lemma check_evalmany_4: evalmany (tm_plus (tm_const zero) (tm_plus (tm_const two) (tm_plus (tm_const zero) (tm_const three)))) (tm_plus (tm_const zero) (tm_const (plus two (plus zero three)))). Proof. (* FILL IN HERE (and delete "Admitted") *) Admitted. (* The second step is to define a "result" of a term [t] as a normal form that [t] can reach by some number of steps of evaluation. Formally, we write [normal_form_of t t'] to mean that [t'] is a normal form reachable from [t] by many-step evaluation. *) (* Writing "normal_form _ eval t" all the time gets a bit ugly. Let's introduce a more pronounceable shorthand: *) Notation eval_normal_form := (normal_form _ eval). Definition normal_form_of (t t' : tm) := (evalmany t t' /\ eval_normal_form t'). (* We have already seen that single-step evaluation is deterministic -- i.e., a given term can take a single step in at most one way. It follows from this that, if [t] can reach a normal form, then this normal form is unique -- i.e., [normal_form_of] is a partial function. *) Theorem normal_forms_unique : partial_function _ normal_form_of. Proof. (* The proof of this still needs to be written. *) Admitted. (* Indeed, something stronger is true for this language (though not for all programming languages!): the evaluation of ANY term [t] will eventually reach a normal form -- i.e., [normal_form_of] is a TOTAL function. Formally, we say [normal_form_of] is NORMALIZING. *) Definition normalizing (X:Set) (R:relation X) := forall t, exists t', (refl_trans_closure _ R) t t' /\ normal_form _ R t'. (* To prove that [normal_form_of] is normalizing, we need a few lemmas. First, we observe that, if [t] evaluates to [t'] in many steps, then the same sequence of evaluation steps is possible when [t] appears as the left-hand child of a [tm_plus] node, and similarly when [t] appears as the right-hand child of a [tm_plus] node whose left-hand child is a value. *) Lemma evalmany_congr_1 : forall t1 t1' t2, evalmany t1 t1' -> evalmany (tm_plus t1 t2) (tm_plus t1' t2). Proof. intros t1 t1' t2 H. induction H. Case "rtc_R". apply rtc_R. apply E_Plus1. apply H. Case "rtc_refl". apply rtc_refl. Case "rtc_trans". apply rtc_trans with (y := (tm_plus y t2)). apply IHrefl_trans_closure1. apply IHrefl_trans_closure2. Qed. Lemma evalmany_congr_2 : forall t1 t2 t2', value t1 -> evalmany t2 t2' -> evalmany (tm_plus t1 t2) (tm_plus t1 t2'). Proof. (* FILL IN HERE (and delete "Admitted") *) Admitted. (* Next, let's break the [value_iff_nf] into two separate lemmas, one for each direction. (Coq's facilities for dealing "in-line" with <-> statements are a little awkward.) *) Lemma nf__value : forall t, eval_normal_form t -> value t. Proof. intros t H. assert (value t <-> eval_normal_form t) as L. apply value_iff_nf. destruct L. apply H1. apply H. Qed. Lemma value__nf : forall t, value t -> eval_normal_form t. Proof. intros t H. assert (value t <-> eval_normal_form t) as L. apply value_iff_nf. destruct L. apply H0. apply H. Qed. Theorem eval_normalizing : normalizing _ eval. Proof. unfold normalizing. induction t. Case "tm_const". apply ex_intro with (witness := tm_const n). apply conj. Case "l". apply rtc_refl. Case "r". apply value__nf. apply v_const. Case "tm_plus". destruct IHt1. destruct IHt2. destruct H. destruct H0. apply nf__value in H1. apply nf__value in H2. inversion H1. inversion H2. rewrite <- H3 in H. rewrite <- H4 in H0. apply ex_intro with (witness := tm_const (plus n n0)). apply conj. Case "l". apply rtc_trans with (y := tm_plus (tm_const n) t2). apply evalmany_congr_1. apply H. apply rtc_trans with (y := tm_plus (tm_const n) (tm_const n0)). apply evalmany_congr_2. apply v_const. apply H0. apply rtc_R. apply E_PlusConstConst. Case "r". apply value__nf. apply v_const. Qed. Lemma normal_form_to_forall : forall t, eval_normal_form t -> forall u, ~ eval t u. Proof. (* FILL IN HERE (and delete "Admitted") *) Admitted. Lemma normal_form_from_forall : forall t, (forall u, ~ eval t u) -> eval_normal_form t. Proof. (* FILL IN HERE (and delete "Admitted") *) Admitted. End SimpleArithEvalContd'. Notation normalizing := SimpleArithEvalContd'.normalizing. (* ====================================================================== *) (* OK, enough about this ridiculously simple language! Let's move on to something a tiny bit more interesting... *) (* --------------------------------------------------------------------- *) (** * The full arithmetic language *) Module FullArith. Inductive tm : Set := | tm_true : tm | tm_false : tm | tm_if : tm -> tm -> tm -> tm | tm_zero : tm | tm_succ : tm -> tm | tm_pred : tm -> tm | tm_iszero : tm -> tm. Inductive bvalue : tm -> Prop := | bv_true : bvalue tm_true | bv_false : bvalue tm_false. Inductive nvalue : tm -> Prop := | nv_zero : nvalue tm_zero | nv_succ : forall t, nvalue t -> nvalue (tm_succ t). Definition value (t:tm) := bvalue t \/ nvalue t. Inductive eval : tm -> tm -> Prop := | E_IfTrue : forall t1 t2, eval (tm_if tm_true t1 t2) t1 | E_IfFalse : forall t1 t2, eval (tm_if tm_false t1 t2) t2 | E_If : forall t1 t1' t2 t3, eval t1 t1' -> eval (tm_if t1 t2 t3) (tm_if t1' t2 t3) | E_Succ : forall t1 t1', eval t1 t1' -> eval (tm_succ t1) (tm_succ t1') | E_PredZero : eval (tm_pred tm_zero) tm_zero | E_PredSucc : forall t1, nvalue t1 -> eval (tm_pred (tm_succ t1)) t1 | E_Pred : forall t1 t1', eval t1 t1' -> eval (tm_pred t1) (tm_pred t1') | E_IszeroZero : eval (tm_iszero tm_zero) tm_true | E_IszeroSucc : forall t1, nvalue t1 -> eval (tm_iszero (tm_succ t1)) tm_false | E_Iszero : forall t1 t1', eval t1 t1' -> eval (tm_iszero t1) (tm_iszero t1'). Notation eval_normal_form := (normal_form _ eval). (* The first interesting thing about this language is that the progress theorem fails! That is, there are terms that are normal forms -- they can't take an evaluation step -- but not values -- i.e., we have not included them in our definition of possible "results of evaluation." Such terms are said to be STUCK. *) Definition stuck (t:tm) : Prop := eval_normal_form t /\ ~ value t. Theorem not_eval_progress : exists t, stuck t. Proof. (* FILL IN HERE (and delete "Admitted") *) Admitted. (* Fortunately, things are not completely messed up: values and normal forms are not the same in this language, but at least the former set is included in the latter (i.e., we did not accidentally define things so that some value could still take a step). *) Lemma value__nf : forall t, value t -> eval_normal_form t. Proof. (* Hint: You will reach a point in this proof where you need to use an induction to reason about a term that is known to be a numeric value. This induction can be performed either over the term itself or over the evidence that it is a numeric value. The proof goes through in either case, but you will find that one way is quite a bit shorter than the other. For the sake of the exercise, try to complete the proof both ways. *) (* FILL IN HERE (and delete "Admitted") *) Admitted. (* Here is a lemma that is a corollary of the previous fact (i.e., that follows immediately from it) but repackages its content in a way that will be convenient to use in later proofs. *) Lemma values_don't_step : forall t t' P, value t -> eval t t' -> P. Proof. intros t t' P Hv He. apply value__nf in Hv. unfold normal_form in Hv. unfold not in Hv. assert (exists t', eval t t') as G. Case "Proof of assertion". apply ex_intro with (witness := t'). apply He. apply Hv in G. inversion G. Qed. (* ---------------------------------------------------------------------- *) (* A little proof engineering *) (* The next thing to show is that the evaluation relation for this larger language is still deterministic. Since this involves a case analysis of two evaluation derivations, each of which may end with any of ten constructors, this proof starts to get a little long! This will provide a good opportunity for introducing a few of Coq's automation features. *) Theorem eval_deterministic : partial_function _ eval. Proof. (* Proof sketch: The argument is very similar to the one we gave for the simpler language a couple of lectures ago. We perform induction on one of the given derivations and inversion on the other; most of the cases that result are contradictory and the remainder use the induction hypothesis. *) unfold partial_function. intros x y1 y2 Hy1 Hy2. generalize dependent y2. induction Hy1. Case "E_IfTrue". intros y2 Hy2. inversion Hy2. Case "Hy2 by E_IfTrue". reflexivity. Case "Hy2 by E_If". inversion H3. Case "E_IfFalse". intros y2 Hy2. inversion Hy2. Case "Hy2 by E_IfFalse". reflexivity. Case "Hy2 by E_If". inversion H3. Case "E_If". intros y2 Hy2. inversion Hy2. Case "Hy2 by E_IfTrue". rewrite <- H0 in Hy1. inversion Hy1. Case "Hy2 by E_IfFalse". rewrite <- H0 in Hy1. inversion Hy1. Case "Hy2 by E_If". apply IHHy1 in H3. rewrite -> H3. reflexivity. Case "E_Succ". intros y2 Hy2. inversion Hy2. apply IHHy1 in H0. rewrite -> H0. reflexivity. Case "E_PredZero". intros y2 Hy2. inversion Hy2. Case "Hy2 by E_PredZero". reflexivity. Case "Hy2 by E_Pred". inversion H0. Case "E_PredSucc". intros y2 Hy2. inversion Hy2. Case "Hy2 by E_PredSucc". reflexivity. Case "Hy2 by E_Pred". inversion H1. assert (value t1) as V. Case "Proof of assertion". unfold value. apply or_intror. apply H. apply values_don't_step with (t := t1) (t':=t1'0). apply V. apply H4. Case "E_Pred". intros y2 Hy2. inversion Hy2. Case "Hy2 by E_PredZero". rewrite <- H0 in Hy1. inversion Hy1. Case "Hy2 by E_PredSucc". rewrite <- H in Hy1. inversion Hy1. assert (value y2) as V. Case "Proof of assertion". unfold value. apply or_intror. apply H0. apply values_don't_step with (t := y2) (t':=t1'0). apply V. rewrite -> H1 in H3. apply H3. Case "Hy2 by E_Pred". subst. apply IHHy1 with (y2:=t1'0) in H0. rewrite -> H0. reflexivity. Case "E_IsZeroZero". intros y2 Hy2. inversion Hy2. Case "Hy2 by E_IszeroZero". reflexivity. Case "Hy2 by E_Iszero". inversion H0. Case "E_IszeroSucc". intros y2 Hy2. inversion Hy2. Case "Hy2 by E_IszeroSucc". reflexivity. Case "Hy2 by E_Iszero". inversion H1. assert (value t1) as V. Case "Proof of assertion". unfold value. apply or_intror. apply H. apply values_don't_step with (t := t1) (t':=t1'0). apply V. apply H4. Case "E_Iszero". intros y2 Hy2. inversion Hy2. Case "Hy2 by E_IszeroZero". rewrite <- H0 in Hy1. inversion Hy1. Case "Hy2 by E_IszeroSucc". rewrite <- H in Hy1. inversion Hy1. assert (value t0) as V. Case "Proof of assertion". unfold value. apply or_intror. apply H0. apply values_don't_step with (t := t0) (t':=t1'0). apply V. apply H3. Case "Hy2 by E_Iszero". apply IHHy1 with (y2:=t1'0) in H0. rewrite -> H0. reflexivity. Qed. (* First, a little easy tidying. The uses of [inversion] in this proof (and other proofs that we have seen) tend to introduce a lot of equalities that can make the context hard to understand. The [subst] tactic eliminates (from the context) ALL equalities where one side is just a variable by rewriting all occurrences of this variable in the goal and all the other hypotheses. This is often a good way to clean up a mess left by [inversion]. *) Theorem eval_deterministic' : partial_function _ eval. Proof. unfold partial_function. intros x y1 y2 Hy1 Hy2. generalize dependent y2. induction Hy1. Case "E_IfTrue". intros y2 Hy2. inversion Hy2. Case "Hy2 by E_IfTrue". reflexivity. Case "Hy2 by E_If". inversion H3. Case "E_IfFalse". intros y2 Hy2. inversion Hy2. Case "Hy2 by E_IfFalse". reflexivity. Case "Hy2 by E_If". inversion H3. Case "E_If". intros y2 Hy2. inversion Hy2. Case "Hy2 by E_IfTrue". subst. inversion Hy1. Case "Hy2 by E_IfFalse". subst. inversion Hy1. Case "Hy2 by E_If". subst. apply IHHy1 in H3. subst. reflexivity. Case "E_Succ". intros y2 Hy2. inversion Hy2. apply IHHy1 in H0. subst. reflexivity. Case "E_PredZero". intros y2 Hy2. inversion Hy2. Case "Hy2 by E_PredZero". reflexivity. Case "Hy2 by E_Pred". inversion H0. Case "E_PredSucc". intros y2 Hy2. inversion Hy2. Case "Hy2 by E_PredSucc". reflexivity. Case "Hy2 by E_Pred". inversion H1. subst. assert (value t1) as V. Case "Proof of assertion". unfold value. apply or_intror. apply H. apply values_don't_step with (t := t1) (t':=t1'0). apply V. apply H4. Case "E_Pred". intros y2 Hy2. inversion Hy2. Case "Hy2 by E_PredZero". subst. inversion Hy1. Case "Hy2 by E_PredSucc". subst. inversion Hy1. subst. assert (value y2) as V. Case "Proof of assertion". unfold value. apply or_intror. apply H0. apply values_don't_step with (t := y2) (t':=t1'0). apply V. apply H1. Case "Hy2 by E_Pred". subst. apply IHHy1 with (y2:=t1'0) in H0. subst. reflexivity. (* The last bit of the proof is left for you to practice on. Start by cutting and pasting the corresponding cases from the previous version. Then find as many places as you can where [subst] can be used instead of [rewrite]. *) (* FILL IN HERE (and delete "Admitted") *) Admitted. (* One quickly notices, when carrying out such a long proof, that it is easy to get confused about which case/subcase/etc. is currently active, especially when going back over finished parts of the proof to tidy them up. Our "Case" tactic helps with this, but it can be made even more useful. Here is a better version (called CASE, to avoid going back and changing everywhere we have used Case already). *) 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." ]. Ltac CASE name := Case_aux CASE name. Ltac SUBCASE name := Case_aux SUBCASE name. Ltac SSUBCASE name := Case_aux SSUBCASE name. Ltac SSSUBCASE name := Case_aux SSSUBCASE name. Ltac SSSSUBCASE name := Case_aux SSSSUBCASE name. Tactic Notation "eval_cases" tactic(first) tactic(c) := first; [ c "E_IfTrue" | c "E_IfFalse" | c "E_If" | c "E_Succ" | c "E_PredZero" | c "E_PredSucc" | c "E_Pred" | c "E_IszeroZero" | c "E_IszeroSucc" | c "E_Iszero" ]. (* As before, there is no need to understand the details of how these [Ltac] definitions work. What's important is how we use them. If [H] is a hypothesis of type [eval t t'], then doing eval_cases (induction H) (CASE) will perform an induction on [H] (the same as if we had just typed [induction H]) and ALSO add a "CASE" tag to each subgoal labeling which constructor it comes from. The tactic [CASE "foo"] , on the other hand, behaves exactly like the old [Case "foo"] except that it checks whether there is already a "CASE" tag in the context and, if there is, it verifies that its value is "foo". The tactics [SUBCASE], [SSUBCASE], etc., work the same as [CASE] but use the tags "SUBCASE", "SSUBCASE", etc. *) Theorem eval_deterministic'' : partial_function _ eval. Proof. unfold partial_function. intros x y1 y2 Hy1 Hy2. generalize dependent y2. eval_cases (induction Hy1) (CASE). CASE "E_IfTrue". intros y2 Hy2. inversion Hy2. SUBCASE "Hy2 by E_IfTrue". reflexivity. SUBCASE "Hy2 by E_If". inversion H3. CASE "E_IfFalse". intros y2 Hy2. inversion Hy2. SUBCASE "Hy2 by E_IfFalse". reflexivity. SUBCASE "Hy2 by E_If". inversion H3. CASE "E_If". intros y2 Hy2. inversion Hy2. SUBCASE "Hy2 by E_IfTrue". subst. inversion Hy1. SUBCASE "Hy2 by E_IfFalse". subst. inversion Hy1. SUBCASE "Hy2 by E_If". subst. apply IHHy1 in H3. subst. reflexivity. CASE "E_Succ". intros y2 Hy2. inversion Hy2. apply IHHy1 in H0. subst. reflexivity. CASE "E_PredZero". intros y2 Hy2. inversion Hy2. SUBCASE "Hy2 by E_PredZero". reflexivity. SUBCASE "Hy2 by E_Pred". inversion H0. CASE "E_PredSucc". intros y2 Hy2. inversion Hy2. SUBCASE "Hy2 by E_PredSucc". reflexivity. SUBCASE "Hy2 by E_Pred". inversion H1. subst. assert (value t1) as V. SSUBCASE "Proof of assertion". unfold value. apply or_intror. apply H. apply values_don't_step with (t := t1) (t':=t1'0). apply V. apply H4. CASE "E_Pred". intros y2 Hy2. inversion Hy2. SUBCASE "Hy2 by E_PredZero". subst. inversion Hy1. SUBCASE "Hy2 by E_PredSucc". subst. inversion Hy1. subst. assert (value y2) as V. SSUBCASE "Proof of assertion". unfold value. apply or_intror. apply H0. apply values_don't_step with (t := y2) (t':=t1'0). apply V. apply H1. SUBCASE "Hy2 by E_Pred". subst. apply IHHy1 with (y2:=t1'0) in H0. subst. reflexivity. CASE "E_IszeroZero". intros y2 Hy2. inversion Hy2. SUBCASE "Hy2 by E_IszeroZero". reflexivity. SUBCASE "Hy2 by E_Iszero". inversion H0. (* Cut and paste the last few cases from the previous version. Then replace all occurrences of [Case] by [CASE], [SUBCASE]. etc., as appropriate. *) (* FILL IN HERE (and delete "Admitted") *) Admitted. (* Next issue: Many cases of this proof are very similar -- so similar that we could almost do the later ones just by cutting and pasting the earlier ones. However, this usually doesn't quite work because, in each case, the set of hypotheses is slightly different, which means that the one we need has a different name each time. The [assumption] tactic helps with this. If the context contains an assumption [H] that will solve the goal and generate no subgoals, then doing [assumption] is just the same as doing [apply H], except that [H] does not need to be named explicitly. *) Theorem eval_deterministic''' : partial_function _ eval. Proof. unfold partial_function. intros x y1 y2 Hy1 Hy2. generalize dependent y2. eval_cases (induction Hy1) (CASE). CASE "E_IfTrue". intros y2 Hy2. inversion Hy2. SUBCASE "Hy2 by E_IfTrue". reflexivity. SUBCASE "Hy2 by E_If". inversion H3. CASE "E_IfFalse". intros y2 Hy2. inversion Hy2. SUBCASE "Hy2 by E_IfFalse". reflexivity. SUBCASE "Hy2 by E_If". inversion H3. CASE "E_If". intros y2 Hy2. inversion Hy2. SUBCASE "Hy2 by E_IfTrue". subst. inversion Hy1. SUBCASE "Hy2 by E_IfFalse". subst. inversion Hy1. SUBCASE "Hy2 by E_If". subst. apply IHHy1 in H3. subst. reflexivity. CASE "E_Succ". intros y2 Hy2. inversion Hy2. apply IHHy1 in H0. subst. reflexivity. CASE "E_PredZero". intros y2 Hy2. inversion Hy2. SUBCASE "Hy2 by E_PredZero". reflexivity. SUBCASE "Hy2 by E_Pred". inversion H0. CASE "E_PredSucc". intros y2 Hy2. inversion Hy2. SUBCASE "Hy2 by E_PredSucc". reflexivity. SUBCASE "Hy2 by E_Pred". inversion H1. subst. assert (value t1) as V. SSUBCASE "Proof of assertion". unfold value. apply or_intror. assumption. apply values_don't_step with (t := t1) (t':=t1'0). assumption. assumption. CASE "E_Pred". intros y2 Hy2. inversion Hy2. SUBCASE "Hy2 by E_PredZero". subst. inversion Hy1. SUBCASE "Hy2 by E_PredSucc". subst. inversion Hy1. subst. assert (value y2) as V. SSUBCASE "Proof of assertion". unfold value. apply or_intror. assumption. apply values_don't_step with (t := y2) (t':=t1'0). assumption. assumption. SUBCASE "Hy2 by E_Pred". subst. apply IHHy1 with (y2:=t1'0) in H0. subst. reflexivity. CASE "E_IszeroZero". intros y2 Hy2. inversion Hy2. SUBCASE "Hy2 by E_IszeroZero". reflexivity. SUBCASE "Hy2 by E_Iszero". inversion H0. (* Find as many places as possible where [assumption] can be used. *) (* FILL IN HERE (and delete "Admitted") *) Admitted. (* A similar annoyance arises in situations where the context contains a contradictory assumption and we want to use [inversion] on it to solve the goal. We'd like to be able to say to Coq, "find a contradictory assumption and invert it" without giving its name explicitly. Unfortunately (and a bit surprisingly), Coq does not provide a built-in tactic that does this. However, it is not too hard to define one ourselves. (Thanks to Aaron Bohannon for this nice hack.) *) Tactic Notation "solve_by_inversion_step" tactic(t) := match goal with | H : _ |- _ => solve [ inversion H; subst; t ] end || fail "because the goal is not solvable by inversion.". Tactic Notation "solve" "by" "inversion" "1" := solve_by_inversion_step idtac. Tactic Notation "solve" "by" "inversion" "2" := solve_by_inversion_step (solve by inversion 1). Tactic Notation "solve" "by" "inversion" "3" := solve_by_inversion_step (solve by inversion 2). Tactic Notation "solve" "by" "inversion" := solve by inversion 1. (* Again, the details of how the [Ltac] definition works are not important. All we need to know is that doing [solve by inversion] will find a hypothesis that can be inverted to solve the goal, if there is one. The tactics [solve by inversion 2] and [solve by inversion 3] are slightly fancier versions which will perform two or three inversions in a row, if necessary, to solve the goal. *) Theorem eval_deterministic'''' : partial_function _ eval. Proof. unfold partial_function. intros x y1 y2 Hy1 Hy2. generalize dependent y2. eval_cases (induction Hy1) (CASE). CASE "E_IfTrue". intros y2 Hy2. inversion Hy2. SUBCASE "Hy2 by E_IfTrue". reflexivity. SUBCASE "Hy2 by E_If". solve by inversion. CASE "E_IfFalse". intros y2 Hy2. inversion Hy2. SUBCASE "Hy2 by E_IfFalse". reflexivity. SUBCASE "Hy2 by E_If". solve by inversion. CASE "E_If". intros y2 Hy2. inversion Hy2. SUBCASE "Hy2 by E_IfTrue". subst. solve by inversion. SUBCASE "Hy2 by E_IfFalse". subst. solve by inversion. SUBCASE "Hy2 by E_If". subst. apply IHHy1 in H3. subst. reflexivity. CASE "E_Succ". intros y2 Hy2. inversion Hy2. apply IHHy1 in H0. subst. reflexivity. CASE "E_PredZero". intros y2 Hy2. inversion Hy2. SUBCASE "Hy2 by E_PredZero". reflexivity. SUBCASE "Hy2 by E_Pred". solve by inversion. (* Find as many places as possible where [solve by inversion] can be used. *) (* FILL IN HERE (and delete "Admitted") *) Admitted. (* Now we come to something quite a bit more more powerful. With all the polishing we've done on the determinism proof so far, we've managed to reveal quite a bit of common structure. In fact, it looks quite repetitive! In particular, notice that every branch of the outer induction begins with [intros y2 Hy2. inversion Hy2.] and most of the subcases of the inversion begin with a [subst]. We could save a lot of typing if we could tell Coq, at the point where we do [induction Hy1], to perform these operations on EVERY subgoal generated by the [induction] tactic. This effect can be achieved using the [;] operator. In Coq termonology, a TACTIC is an operation that changes Coq's proof state in some way (by solving the main goal, rewriting the goal, rewriting a hypothesis, etc., etc.). On the other hand, [;] is a TACTICAL -- that is, it is a function from tactics to tactics. If [t1] and [t2] are tactics, then [t1 ; t2] is a tactic that first performs [t1] and then, on EACH subgoal generated by [t1], performs [t2]. Similarly, [t1 ; t2 ; t3] first performs [t1], then does [t2] on each subgoal generated by [t1], and then does [t3] on each subgoal generated by [t2]. For example, we can use [;] in the determinism proof to hoist the common [intros y2 Hy2; inversion Hy2; subst.] to the top of the induction. *) Theorem eval_deterministic''''' : partial_function _ eval. Proof. unfold partial_function. intros x y1 y2 Hy1 Hy2. generalize dependent y2. (eval_cases (induction Hy1) (CASE)); intros y2 Hy2; inversion Hy2; subst. CASE "E_IfTrue". SUBCASE "Hy2 by E_IfTrue". reflexivity. SUBCASE "Hy2 by E_If". solve by inversion. CASE "E_IfFalse". SUBCASE "Hy2 by E_IfFalse". reflexivity. SUBCASE "Hy2 by E_If". solve by inversion. CASE "E_If". SUBCASE "Hy2 by E_IfTrue". solve by inversion. SUBCASE "Hy2 by E_IfFalse". solve by inversion. SUBCASE "Hy2 by E_If". apply IHHy1 in H3. subst. reflexivity. CASE "E_Succ". apply IHHy1 in H0. subst. reflexivity. CASE "E_PredZero". SUBCASE "Hy2 by E_PredZero". reflexivity. SUBCASE "Hy2 by E_Pred". solve by inversion. CASE "E_PredSucc". SUBCASE "Hy2 by E_PredSucc". reflexivity. SUBCASE "Hy2 by E_Pred". inversion H1. subst. assert (value t1) as V. SSUBCASE "Proof of assertion". unfold value. apply or_intror. assumption. apply values_don't_step with (t := t1) (t':=t1'0). assumption. assumption. CASE "E_Pred". SUBCASE "Hy2 by E_PredZero". solve by inversion. SUBCASE "Hy2 by E_PredSucc". inversion Hy1. subst. assert (value y2) as V. SSUBCASE "Proof of assertion". unfold value. apply or_intror. assumption. apply values_don't_step with (t := y2) (t':=t1'0). assumption. assumption. SUBCASE "Hy2 by E_Pred". apply IHHy1 with (y2:=t1'0) in H0. subst. reflexivity. (* Copy and paste the last cases of the previous version and modify them so that they work with the context generated by the [eval_cases; ...] line above. *) (* FILL IN HERE (and delete "Admitted") *) Admitted. (* Doing the same thing to all subgoals is very useful. But notice, in the proof as it now stands, that many of the subcases are solved by either [reflexivity] or [solve by inversion]. It would be really nice to deal with all of these once and for all. I.e., what we want is a way to do the same thing to MOST of the subgoals! The [try solve ...] tactical gives us this ability. If [t] is a tactic, then [try solve [t]] is a tactic that - if [t] solves the goal, behaves just like [t], or - if [t] cannot completely solve the goal, does nothing. More generally, [try solve [t1 | t2 | ...]] will try to solve the goal by using [t1], [t2], etc. If none of them succeeds in completely solving the goal, then [try solve [t1 | t2 | ...]] does nothing. The fact that it does nothing when it doesn't completely succeed in solving the goal means that there is no harm in using [try solve ...] in situations where it makes no sense. In particular, if we put it after a [;], it will solve as many subgoals as it can and leave the rest for us to solve by other methods. Doing [try solve [reflexivity | solve by inversion]] on each of the subgoals generated by the [induction... inversion...] at the top of the determinism proof removes a lot of silly subgoals and leaves us free to concentrate on the more interesting ones. *) Theorem eval_deterministic'''''' : partial_function _ eval. Proof. unfold partial_function. intros x y1 y2 Hy1 Hy2. generalize dependent y2. (eval_cases (induction Hy1) (CASE)); intros y2 Hy2; inversion Hy2; subst; try solve [reflexivity | solve by inversion]. CASE "E_If". SUBCASE "Hy2 by E_If". apply IHHy1 in H3. subst. reflexivity. CASE "E_Succ". apply IHHy1 in H0. subst. reflexivity. CASE "E_PredSucc". SUBCASE "Hy2 by E_Pred". inversion H1. subst. assert (value t1) as V. SSUBCASE "Proof of assertion". unfold value. apply or_intror. assumption. apply values_don't_step with (t := t1) (t':=t1'0). assumption. assumption. (* Copy and paste the end the previous version and modify it so that it works here. Note that some cases will disappear completely. *) (* FILL IN HERE (and delete "Admitted") *) Admitted. End FullArith.