(** * Software Foundations, Formally Benjamin C. Pierce Version of 10/22/2007 *) Require Export lec11_sol. (* ====================================================================== *) (* LECTURE 12 *) (* Where we've been... - Functional programming -- inductive definitions of datatypes -- Fixpoints over inductive datatypes -- higher-order functions (map, fold, filter, etc.) -- polymorphism -- dependent types - Logic -- inductive definitions of propositions and relations -- logical connectives as inductive propositions -- inductive proof techniques (induction principles, generalizing induction hypotheses, etc. -- the Curry-Howard isomorphism between proofs and programs - Coq -- fundamental tactics -- basic automation - Operational semantics -- programming language syntax as an inductive datatype -- single- and multi-step evaluation relations -- determinism, progress, normalization Where we're going... - Types -- typing relations -- type safety (progress and preservation theorems) - The lambda-calculus -- untyped lambda-calculus: the ur-functional programming language -- formalizing bound variables and substitution -- function types - Object-oriented features -- Record types -- Subtyping - Major case study: Featherweight Java Fundamental themes of the course (i.e., What's it all good for?)... - Logic: The mathematical basis for ALL of computer science logic calculus -------------------- = ---------------------------- software engineering mechanical/civil engineering -- In particular, inductively defined sets and relations and inductive proofs about them are ubiquitous in computer science - Coq: An industrial-strength proof assistant -- Proof assistants are becoming more and more popular in both software and (especially) hardware industries. Coq is not the only one in widespread use, but learning one thoroughly will give you a big advantage in coming to grips with another. - Functional programming: An increasingly important part of the software developer's bag of tricks -- Advanced programming idioms in mainstream software development methodologies are increasingly incorporating ideas from functional programming. -- In particular, using persistent data structures and avoiding mutable state enormously simplifies many concurrent programming tasks. - Foundations of programming languages (the second part of the course): -- Notations and techniques for rigorously describing and stress-testing new programming languages and language features. (This is a surprisingly common activity! Most large software systems include subsystems that are basically programming languages -- think regular expressions, command-line formats, preference and configuration files, SQL, Flash, PDF, etc., etc.) -- A more sophisticated understanding of the everyday tools used to build software... what's going on under the hood of . *) (* --------------------------------------------------------------------- *) (* A single-step evaluation FUNCTION *) (* One last thing to look at in our small language of booleans and numbers... *) Module FullArithContd. Export FullArith. (* We saw in lecture 5 how to define a function that behaves as a single-step evaluator for the trivial programming language we were using in that lecture. Here is the analogous function for our current language... *) Fixpoint is_nvalue (t:tm) {struct t} : yesno := match t with | tm_zero => yes | tm_succ t1 => is_nvalue t1 | _ => no end. (* Wei: An older version of this lecture has bugs in this definition *) Fixpoint simplify_step (t:tm) {struct t} : option tm := match t with | tm_if t1 t2 t3 => match simplify_step t1 with | None => match t1 with | tm_true => Some _ t2 | tm_false => Some _ t3 | _ => None _ end | Some t1' => Some _ (tm_if t1' t2 t3) end | tm_succ t1 => match simplify_step t1 with | None => None _ | Some t1' => Some _ (tm_succ t1') end | tm_pred t1 => match simplify_step t1 with | None => match t1 with | tm_zero => Some _ tm_zero | tm_succ t2 => if (is_nvalue t2) then Some _ t2 else None _ | _ => None _ end | Some t1' => Some _ (tm_pred t1') end | tm_iszero t1 => match simplify_step t1 with | None => match t1 with | tm_zero => Some _ tm_true | tm_succ t2 => if (is_nvalue t2) then Some _ tm_false else None _ | _ => None _ end | Some t1' => Some _ (tm_iszero t1') end | _ => None _ end. (* This function is a little complicated, but its logic closely mirrors the rules of the evaluation relation -- things are just a little reorganized because, in the evaluation relation, the preconditions for each rule are listed all at once, while the evaluation function performs several nested tests (by pattern matching) one after another. We can make this correspondence precise by PROVING that we've defined the single-step function correctly, i.e., that it computes exactly the same partial function as the evaluation relation -- Theorem [two_variants_of_single_step_evaluation_coincide] below. (Thanks to Luke Zarko for help with this proof.) *) Lemma is_nvalue__nvalue : forall t, is_nvalue t = yes -> nvalue t. Proof. induction t; try solve [intros H; solve by inversion | reflexivity]. intros H. apply nv_zero. intros H. apply nv_succ. apply IHt. inversion H. reflexivity. Qed. Lemma nvalue__is_nvalue : forall t, nvalue t -> is_nvalue t = yes. Proof. intros t H. induction H. reflexivity. simpl. assumption. Qed. Lemma nvalues_do_not_step : forall t, nvalue t -> simplify_step t = None _. intros t H. induction H; try solve [intros H; solve by inversion | reflexivity]. simpl. rewrite -> IHnvalue. reflexivity. Qed. (* Wei: This was a failed attempt to prove for the buggy version of simplify_step Theorem two_variants_of_single_step_evaluation_coincide : forall t t', eval t t' <-> simplify_step t = Some _ t'. Proof. (* Wei: [info] tells you what primitive tactics are used under the hood *) info unfold iff. (* [Show Intro] tells you what name Coq would pick *) Show Intro. (* [info intro] does the same thing, plus do the real intro *) info intro. intuition. (* -> *) induction H; trivial. simpl; destruct t1; try solve by inversion; rewrite IHeval; trivial. simpl; rewrite IHeval; trivial. simpl. assert (forall t, nvalue t -> is_nvalue t = yes) as nvalue_lemma. induction 1; trivial. cut (is_nvalue t1 = yes). simpl. intros. rewrite H0. trivial. simpl. apply nvalue_lemma. trivial. (* Wei: Here we get stuck, due to the bug in simplify_step *) destruct t1; try solve by inversion. simpl. *) Theorem two_variants_of_single_step_evaluation_coincide : forall t t', eval t t' <-> simplify_step t = Some _ t'. Proof. unfold iff. intros t t'. apply conj. CASE "->". intros H. (eval_cases (induction H) (SUBCASE)); try solve [simpl; reflexivity]. SUBCASE "E_If". simpl. rewrite -> IHeval. destruct t1; try solve [solve by inversion | reflexivity]. SUBCASE "E_Succ". simpl. rewrite -> IHeval. reflexivity. SUBCASE "E_PredSucc". apply nvalue__is_nvalue in H. simpl. rewrite -> H. apply is_nvalue__nvalue in H. apply nvalues_do_not_step in H. rewrite -> H. reflexivity. simpl. rewrite -> IHeval. reflexivity. SUBCASE "E_IszeroSucc". apply nvalue__is_nvalue in H. simpl. rewrite -> H. apply is_nvalue__nvalue in H. apply nvalues_do_not_step in H. rewrite -> H. reflexivity. simpl. rewrite -> IHeval. reflexivity. CASE "<-". generalize dependent t'. induction t; intros t' H; try solve [solve by inversion]. SUBCASE "tm_if". inversion H. destruct (simplify_step t1). inversion H1. apply E_If. apply IHt1. reflexivity. destruct t1; try solve [solve by inversion]. inversion H. apply E_IfTrue. inversion H. apply E_IfFalse. SUBCASE "tm_succ". inversion H. destruct (simplify_step t). inversion H1. apply E_Succ. apply IHt. reflexivity. inversion H1. SUBCASE "tm_pred". inversion H. destruct (simplify_step t). inversion H1. apply E_Pred. apply IHt. reflexivity. destruct t; try solve [solve by inversion]. inversion H1. apply E_PredZero. assert (is_nvalue t = yes). destruct (is_nvalue t). reflexivity. solve by inversion. rewrite -> H0 in H1. inversion H1. subst. apply E_PredSucc. apply is_nvalue__nvalue in H0. apply H0. SUBCASE "tm_iszero". inversion H. destruct (simplify_step t). inversion H1. apply E_Iszero. apply IHt. reflexivity. destruct t; try solve [solve by inversion]. inversion H1. apply E_IszeroZero. assert (is_nvalue t = yes). destruct (is_nvalue t). reflexivity. solve by inversion. rewrite -> H0 in H1. inversion H1. subst. apply E_IszeroSucc. apply is_nvalue__nvalue in H0. apply H0. Qed. End FullArithContd. (* --------------------------------------------------------------------- *) (* Types *) Module FullArithTypes. Export FullArith. Inductive ty : Set := | ty_bool : ty | ty_nat : ty. Inductive has_type : tm -> ty -> Prop := | T_True : has_type tm_true ty_bool | T_False : has_type tm_false ty_bool | T_If : forall t1 t2 t3 T, has_type t1 ty_bool -> has_type t2 T -> has_type t3 T -> has_type (tm_if t1 t2 t3) T | T_Zero : has_type tm_zero ty_nat | T_Succ : forall t1, has_type t1 ty_nat -> has_type (tm_succ t1) ty_nat | T_Pred : forall t1, has_type t1 ty_nat -> has_type (tm_pred t1) ty_nat | T_Iszero : forall t1, has_type t1 ty_nat -> has_type (tm_iszero t1) ty_bool. Tactic Notation "has_type_cases" tactic(first) tactic(c) := first; [ c "T_True" | c "T_False" | c "T_If" | c "T_Zero" | c "T_Succ" | c "T_Pred" | c "T_Iszero" ]. Theorem progress : forall t T, has_type t T -> value t \/ exists t', eval t t'. Proof. intros t T HT. has_type_cases (induction HT) (CASE). CASE "T_True". apply or_introl. unfold value. apply or_introl. apply bv_true. CASE "T_False". apply or_introl. unfold value. apply or_introl. apply bv_false. CASE "T_If". apply or_intror. destruct IHHT1. SUBCASE "t1 is a value". destruct H. SSUBCASE "t1 is a bvalue". destruct H. SSSUBCASE "t1 is tm_true". apply ex_intro with (witness := t2). apply E_IfTrue. SSSUBCASE "t1 is tm_false". apply ex_intro with (witness := t3). apply E_IfFalse. SSUBCASE "t1 is an nvalue". solve by inversion 2. SUBCASE "t1 can take a step". destruct H. apply ex_intro with (witness := tm_if witness t2 t3). apply E_If. assumption. CASE "T_Zero". left. constructor 2. constructor. CASE "T_Succ". destruct IHHT. left. constructor 2. constructor. (* Wei: It's simpler to destruct the possibilities of [value t1]: destruct H. try solve by inversion 2. trivial. *) (* Following is another way to prove (value t1 -> nvalue t1) by induction on t1, *) induction t1; try solve by inversion 2; constructor. apply IHt1. inversion HT. trivial. inversion H; try solve by inversion 2. inversion H0. constructor 2. trivial. right. destruct H. exists (tm_succ witness). apply E_Succ. trivial. CASE "T_Pred". destruct IHHT. right. cut (nvalue t1). intros. inversion H0. exists tm_zero. apply E_PredZero. exists t. apply E_PredSucc. trivial. (* Use the proof above to prove this assertion *) Admitted. Theorem preservation : forall t t' T, has_type t T -> eval t t' -> has_type t' T. Proof. intros t t' T HT HE. generalize dependent t'. (has_type_cases (induction HT) (CASE)); (* every case needs to introduce a couple of things *) intros t' HE; (* and we can deal with several contradictory cases all at once *) try solve by inversion. CASE "T_If". inversion HE; subst. SUBCASE "E_IfTrue". assumption. SUBCASE "E_IfFalse". assumption. SUBCASE "E_If". apply T_If. apply IHHT1. assumption. assumption. assumption. (* FILL IN HERE (and delete "Admitted") *) Admitted. Theorem preservation' : forall t t' T, has_type t T -> eval t t' -> has_type t' T. Proof. (* Now prove the same property again by induction on the EVALUATION derivation instead of on the typing derivation. Begin by carefully reading and thinking about the first few lines of the above proof to make sure you understand what each one is doing. The set-up for this proof is similar, but not exactly the same. *) intros t t' T HT HE. generalize dependent T. (eval_cases (induction HE) (CASE)); intros; inversion HT; trivial. CASE "E_If". apply T_If. apply IHHE; trivial. trivial. trivial. CASE "E_Succ". apply T_Succ. apply IHHE. trivial. CASE "E_PredSucc". destruct H. apply T_Zero. subst. inversion H1. trivial. Admitted. End FullArithTypes. (* ====================================================================== *) (* A little cleanup: These tactics were accidentally defined inside of a module instead of at the top level. Repeating the definitions here will make them available globally for the rest of the course. *) 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. Ltac SSSSSUBCASE name := Case_aux SSSSSUBCASE name. Ltac SSSSSSUBCASE name := Case_aux SSSSSSUBCASE name. Ltac SSSSSSSUBCASE name := Case_aux SSSSSSSUBCASE name. Ltac SCASE name := Case_aux SCASE name. Ltac SSCASE name := Case_aux SSCASE name. Ltac SSSCASE name := Case_aux SSSCASE name. Ltac SSSSCASE name := Case_aux SSSSCASE name. Ltac SSSSSCASE name := Case_aux SSSSSCASE name. Ltac SSSSSSCASE name := Case_aux SSSSSSCASE name. Ltac SSSSSSSCASE name := Case_aux SSSSSSSCASE name. 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. (* ====================================================================== *) (** * The [remember] tactic *) (* Wei: Also see lec04 *) (* A brief digression to introduce a useful tactic. This material was not covered yet in lecture -- I'll do that next week -- but I'm including it here (a) in case anyone wants to get started with it now and (b) because it is needed to understand the details of the proofs in Lecture 13. *) (* We have seen how the [destruct] tactic can be used to perform case analysis of the results of arbitrary computations. If [e] is an expression whose type is some inductively defined set [T], then, for each constructor [c] of [T], [destruct e] generates a subgoal in which all occurrences of [e] (in the goal and in the context) are replaced by [c]. Sometimes, however, this substitution process loses information that we need in order to complete the proof. For example, suppose we define a function [sillyfun1] like this... *) Definition sillyfun1 (n : nat) : yesno := if eqnat n three then yes else if eqnat n five then yes else no. (* ... and suppose that we want to convince Coq of the rather obvious observation that [sillyfun1 n] yields [yes] only when [n] is odd. By analogy with the proofs we did with [sillyfun] above, it is natural to start the proof like this: *) Lemma sillyfun1_odd_FAILED : forall (n : nat), sillyfun1 n = yes -> odd n = yes. Proof. intros n eq. unfold sillyfun1 in eq. destruct (eqnat n three). (* At this point, we are stuck: the context does not contain enough information to prove the goal! The problem is that the substitution peformed by [destruct] is too brutal -- it threw away every occurrence of [eqnat n three], but we need to keep at least one of these because we need to be able to reason that since, in this branch of the case analysis, [eqnat n three = yes], it must be that [n = three], from which it follows that [n] is odd. *) Admitted. (* What we would really like is not to use [destruct] directly on [eqnat n three] and substitute away all occurrences of this expression, but rather to use [destruct] on something else that is EQUAL to [eqnat n three] -- e.g., if we had a variable that we knew was equal to [eqnat n three], we could [destruct] this variable instead. The [remember] tactic allows us to introduce such a variable. *) Lemma sillyfun1_odd : forall (n : nat), sillyfun1 n = yes -> odd n = yes. Proof. intros n eq. unfold sillyfun1 in eq. remember (eqnat n three) as e3. (* At this point, the context has been enriched with a new variable [e3] and an assumption that [e3 = eqnat n three]. Now if we do [destruct e3]... *) destruct e3. (* ... the variable [e3] gets substituted away (it disappears completely) and we are left with the same state as at the point where we got stuck above, except that the context still contains the extra equality assumption -- now with [yes] substituted for [e3] -- which is exactly what we need to make progress. *) Case "yes". apply eq_symm in Heqe3. apply eqnat_yes in Heqe3. rewrite -> Heqe3. reflexivity. Case "no". (* When we come to the second equality test in the body of the function we are reasoning about, we can use [remember] again in the same way, allowing us to finish the proof. *) remember (eqnat n five) as e5. destruct e5. Case "yes". apply eq_symm in Heqe5. apply eqnat_yes in Heqe5. rewrite -> Heqe5. reflexivity. Case "no". inversion eq. Qed. (* Now you try it... *) Lemma filter_exercise : forall (X : Set) (test : X -> yesno) (x : X) (l l' : list X), filter _ test l = x :: l' -> test x = yes. Proof. induction l; inversion 1. remember (test x0) as x0_satisfy. destruct x0_satisfy. (* test x0 = yes *) Case "yes". inversion H1. rewrite <- H2. symmetry. trivial. (* test x0 = no *) apply IHl with (l':=l'). trivial. Qed.