(** * Software Foundations, Formally Benjamin C. Pierce Version of 10/24/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 lec12. (* ====================================================================== *) (* LECTURE 13 *) (* Now we come to the first really interesting programming language: the lambda-calculus. Chapter 5 of TAPL is good background reading for the material in the next couple of lectures. *) (* ====================================================================== *) (* Untyped lambda-calculus *) Module Lambda. (* When we were doing functional programming back at the beginning of the semester, we saw three fundamental syntactic constructs for creating and using functions: fun x => t abstraction (anonymous function creation) t1 t2 application (apply function t1 to argument t2) x variable (placeholder for the argument to a function, in its body) The PURE LAMBDA-CALCULUS focuses on just these three constructs -- indeed, it offers nothing else. We will see that a surprising range of computational behaviors can be expressed using just these basic building blocks. (Actually, for the sake of convenience when experimenting with the evaluation behavior of terms in the lambda-calculus, we'll include one additional syntactic construct: a collection of uninterpreted constants. These constants have no interesting behavior -- they are computationally "inert" -- and including them does not change the fundamental properties of the language.) *) (* ---------------------------------------------------------------------- *) (* Syntax *) (* To define the lambda-calculus, we begin with names of variables. It doesn't much matter how these are represented -- all we need is to be able to tell whether two names are the same or different. Strings would be a natural choice, but there's an even simpler one: natural numbers. *) Definition name := nat. Definition eqname := eqnat. Definition aa : name := one. Definition bb : name := two. Definition cc : name := three. (* Now the syntax of terms: *) Inductive tm : Set := | tm_const : name -> tm | tm_var : name -> tm | tm_app : tm -> tm -> tm | tm_abs : name -> tm -> tm. Tactic Notation "tm_cases" tactic(first) tactic(c) := first; [ c "tm_const" | c "tm_var" | c "tm_app" | c "tm_abs" ]. (* LATER: There are some [destruct]s below that could be wrapped in [tm_cases] if desired. *) (* Since we are going to be writing some longish examples, it will help to introduce some shorthand notations for terms. Unfortunately, Coq's [Notation] mechanism starts to show its limitations at this point: We can't make a notation for lambda-terms that is as simple and compact as the standard notation used in TAPL. In particular: - The application of one term to another needs to be indicated by some symbol (we use @) rather than by simple juxtaposition as in the standard notation. - In many situations, the [tm_var] constructor needs to be indicated by some symbol (we use !). (In examples, we are often able to get away with writing a bare name and omitting the !, by using Coq's "implicit coercion" mechanism.) Here is a summary of the notations we'll use and their correspondence with the standard notation used in TAPL: Concrete Shorthand notation TAPL Coq -------------------------------------------------------- tm_const c `c c c tm_var x !x or x x x tm_abs x t \x,t lambda x. t fun x => t tm_app t1 t2 t1 @ t2 t1 t2 t1 t2 *) Notation "` n" := (tm_const n) (at level 19). Notation "! n" := (tm_var n) (at level 19). Notation "\ x , t" := (tm_abs x t) (at level 21). Notation "r @ s" := (tm_app r s) (at level 20). (* These declarations tell Coq that it should accept an expression of type [name] in a situation where it wants an expression of type [tm], implicitly inserting the constructor [tm_var] as needed to make such expressions well typed. *) Definition name_in_tm : name -> tm := tm_var. Coercion name_in_tm : name >-> tm. (* For example, the term written lambda aa. lambda bb. aa bb (aa bb) aa in standard notation would be written here either as... *) Check (\aa, \bb, aa @ bb @ (aa @ bb) @ aa). (* ... or, being more explicit about the [tm_var] constructors, as: *) Check (\aa, \bb, !aa @ !bb @ (!aa @ !bb) @ !aa). (* To make examples easier to read, we can define some abbreviations for constants, as we did above for names. *) Notation AA := (tm_const one). Notation BB := (tm_const two). Notation CC := (tm_const three). Notation DD := (tm_const four). Notation EE := (tm_const five). Notation FF := (tm_const six). Check (\aa, aa @ BB @ (aa @ BB) @ aa). (* ---------------------------------------------------------------------- *) (* Evaluation *) Fixpoint only_constants (t:tm) {struct t} : yesno := match t with | tm_const _ => yes | tm_app t1 t2 => both_yes (only_constants t1) (only_constants t2) | _ => no end. Inductive value : tm -> Prop := | v_const : forall t, only_constants t = yes -> value t | v_abs : forall x t, value (\x, t). (* The fundamental "evaluation step" in the lambda-calculus is the application of a function to a value. For example: (\aa, aa) BB ---> BB (\aa, \bb, aa cc) BB ---> \bb, BB cc (\aa, aa) (\bb, bb CC) ---> \bb, bb CC (\aa, aa CC) (\bb, bb) ---> (\bb, bb) CC That is, the fundamental step replaces a term of the form (\x, t) v (where [v] is a value -- a term of this form is often called a BETA REDEX) by the result of substituting [v] for the bound variable [x] in the body [t]. The operation of substituting a value for a variable thus lies at the heart of evaluation in the lambda-calculus. To formalize evaluation, we begin by formalizing substitution... *) Fixpoint subst (x:name) (s:tm) (t:tm) {struct t} : tm := match t with | `c => `c | !y => if eqname x y then s else t | \y, t1 => if eqname x y then t else (\y, subst x s t1) | t1 @ t2 => (subst x s t1) @ (subst x s t2) end. (* LATER: Would it be cleaner to do this as a relation and then define a separate function and show they are equal? Perhaps so! *) (* In TAPL, the substitution of [v] for [x] in the term [t] is written [x |-> v] t but we've already used square brackets for lists. So we'll use curly braces instead. *) Notation "{ x |-> s } t" := (subst x s t) (at level 17). Lemma check_subst1 : {aa |-> CC} (aa @ BB) = CC @ BB. Proof. reflexivity. Qed. (* Here are some examples for you to try... *) (* <------ remove this comment Lemma check_subst2 : {aa |-> (\cc, DD)} BB = FILL IN HERE Proof. reflexivity. Qed. Lemma check_subst3 : {aa |-> (\cc, DD)} (\cc, aa @ (\bb, aa @ DD)) = FILL IN HERE Proof. reflexivity. Qed. Lemma check_subst4 : { aa |-> (\cc, DD)} (\bb, aa @ bb @ (CC @ aa)) = FILL IN HERE Proof. reflexivity. Qed. Lemma check_subst5 : {aa |-> (\cc, DD)} (\cc, aa @ (\aa, DD @ aa)) = FILL IN HERE Proof. reflexivity. Qed. remove this comment ------> *) (* ADD: explain CBV *) Inductive eval : tm -> tm -> Prop := | E_AppAbs : forall x t12 v2, value v2 -> eval ((\x, t12) @ v2) ({x |-> v2} t12) | E_App1 : forall t1 t1' t2, eval t1 t1' -> eval (t1 @ t2) (t1' @ t2) | E_App2 : forall v1 t2 t2', value v1 -> eval t2 t2' -> eval (v1 @ t2) (v1 @ t2'). Lemma check_eval1 : eval ((\aa, aa @ BB) @ CC) (CC @ BB). Proof. rewrite <- check_subst1. apply E_AppAbs. apply v_const. reflexivity. Qed. Lemma check_eval2 : eval ((\aa, \bb, aa @ bb @ (`three @ aa) ) @ (\cc, DD) @ EE) ((\bb, (\cc, DD) @ bb @ (`three @ (\cc, DD))) @ EE). Proof. (* FILL IN HERE (and delete "Admitted") *) Admitted. Lemma check_eval3 : eval ((\aa, aa @ aa @ aa) @ (\aa, aa @ aa)) ((\aa, aa @ aa) @ (\aa, aa @ aa) @ (\aa, aa @ aa)). Proof. (* FILL IN HERE (and delete "Admitted") *) Admitted. (* In passing, let's define an [eval_cases] tactic, for use in inductive proofs about evaluation. *) Tactic Notation "eval_cases" tactic(first) tactic(c) := first; [ c "E_AppAbs" | c "E_App1" | c "E_App2" ]. (* ====================================================================== *) (* An evaluation function for the lambda-calculus *) (* For experimenting with the lamba-calculus, it is convenient to have an evaluation FUNCTION that we can call to normalize terms automatically rather than doing it manually by applying the constructors of the evaluation relation (as in the examples just above). The structure of this function is similar to the one we wrote in the last lecture for the simpler language of booleans and numbers. *) Fixpoint is_value (t:tm) {struct t} : yesno := match t with | \ _, _ => yes | _ => only_constants t end. Fixpoint simplify_step (t:tm) {struct t} : option tm := match t with | t1 @ t2 => if is_value t1 then match simplify_step t2 with | None => match t1 with | \x,t12 => if is_value t2 then Some _ (({x |-> t2} t12)) else None _ | _ => None _ end | Some t2' => Some _ (t1 @ t2') end else match simplify_step t1 with | None => None _ | Some t1' => Some _ (t1' @ t2) end | _ => None _ end. (* As before, we should check our work by proving that [simplify_step] is correctly defined... (I'm not completely satisfied with this proof -- I'll bet it can be done more smoothly, perhaps by slightly tweaking the definition of the evaluation function... However, it does demonstrate that the definition as it stands is correct.) *) Lemma is_value__value : forall t, is_value t = yes -> value t. Proof. intros t0. destruct t0; intros H. simpl. apply v_const. reflexivity. solve by inversion. apply v_const. simpl. simpl in H. assumption. simpl. apply v_abs. Qed. Lemma value__is_value : forall t, value t -> is_value t = yes. Proof. intros t Hv. inversion Hv. destruct t; try solve [reflexivity | solve by inversion]. simpl. simpl in H. assumption. reflexivity. Qed. Lemma both_yes_1 : forall b c, both_yes b c = yes -> b = yes. Proof. intros b c H. destruct b; destruct c; try solve [reflexivity | simpl in H; assumption]. Qed. Lemma both_yes_2 : forall b c, both_yes b c = yes -> c = yes. Proof. intros b c H. destruct b; destruct c; try solve [reflexivity | simpl in H; assumption]. Qed. Lemma only_constants_don't_simplify : forall v, only_constants v = yes -> simplify_step v = None _. Proof. intros v H. (tm_cases (induction v) (CASE)); try solve [reflexivity]. CASE "tm_app". simpl. destruct (is_value v1). SUBCASE "v1 is a value". simpl in H. assert (only_constants v1 = yes). SSUBCASE "Pf of assumption". apply both_yes_1 with (c := only_constants v2). assumption. assert (only_constants v2 = yes). SSUBCASE "Pf of assumption". apply both_yes_2 with (b := only_constants v1). assumption. apply IHv2 in H1. rewrite H1. (tm_cases (destruct v1) (SSUBCASE)). reflexivity. reflexivity. reflexivity. destruct v2; inversion H0. SUBCASE "v1 is not a value". simpl in H. assert (only_constants v1 = yes). SSUBCASE "Pf of assumption". apply both_yes_1 with (c := only_constants v2). assumption. apply IHv1 in H0. rewrite H0. reflexivity. Qed. Lemma simplify_step__eval : forall t t', simplify_step t = Some _ t' -> eval t t'. Proof. intros t t'. generalize dependent t'. (tm_cases (induction t) (CASE)); intros t' H; try solve [solve by inversion]. CASE "tm_app". simpl in H. remember (is_value t1) as r1. destruct r1. SUBCASE "t1 is a value". destruct (simplify_step t2). SSUBCASE "t2 steps". inversion H. apply E_App2. apply is_value__value. apply eq_symm. assumption. apply IHt2. reflexivity. SSUBCASE "t2 nf". destruct t1. SSSUBCASE "tm_const". solve by inversion. SSSUBCASE "tm_var". solve by inversion. SSSUBCASE "tm_app". solve by inversion. SSSUBCASE "tm_abs". remember (is_value t2) as r2. destruct r2. SSSSUBCASE "t2 is a value". inversion H. subst. apply E_AppAbs. apply is_value__value. apply eq_symm. assumption. SSSSUBCASE "t2 is not a value". solve by inversion. SUBCASE "t1 is not a value". remember (simplify_step t1) as r1. destruct r1. SSUBCASE "t1 steps". inversion H. subst. apply E_App1. apply IHt1. reflexivity. SSUBCASE "t1 nf". solve by inversion. Qed. Lemma values_don't_simplify : forall v, value v -> simplify_step v = None _. Proof. intros v Hv. (tm_cases (induction v) (CASE)); try solve [reflexivity]. inversion Hv. apply only_constants_don't_simplify. assumption. Qed. Lemma things_that_simplify_aren't_only_constants : forall t t', simplify_step t = Some _ t' -> only_constants t = no. Proof. intros t t' H. apply simplify_step__eval in H. eval_cases (induction H) (CASE). reflexivity. simpl. rewrite IHeval. reflexivity. simpl. rewrite IHeval. destruct (only_constants v1); reflexivity. Qed. Lemma things_that_simplify_aren't_values : forall t t', simplify_step t = Some _ t' -> is_value t = no. Proof. intros t t' H. assert (only_constants t = no). apply things_that_simplify_aren't_only_constants with (t':=t'). assumption. (tm_cases (destruct t) (CASE)); try solve [solve by inversion]. simpl. simpl in H0. assumption. Qed. 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)). SUBCASE "E_AppAbs". inversion H. simpl. rewrite -> values_don't_simplify. simpl. destruct v2; try solve [solve by inversion]. reflexivity. simpl. simpl in H0. rewrite H0. reflexivity. assumption. simpl. reflexivity. SUBCASE "E_App1". simpl. assert (is_value t1 = no). SSUBCASE "Pf of assertion". apply things_that_simplify_aren't_values with (t' := t1'). assumption. rewrite H0. rewrite IHeval. reflexivity. SUBCASE "E_App2". simpl. destruct v1. SSUBCASE "tm_const". simpl. rewrite IHeval. reflexivity. SSUBCASE "tm_var". simpl. solve by inversion 2. SSUBCASE "tm_app". assert (is_value (v1_1 @ v1_2) = yes). apply value__is_value. assumption. rewrite -> H1. rewrite -> IHeval. reflexivity. SSUBCASE "tm_abs". simpl. rewrite IHeval. reflexivity. CASE "<-". apply simplify_step__eval. Qed. (* THOUGHT EXERCISE (not to be handed in). When I first sat down to write the evaluation function, I did not get it quite right. See if you can spot the error in the following definition without looking at the correct version of [simplify_step] above. (You'll need to look at the definition of [eval], of course.) Test your answer by constructing a term that demonstrates the difference between the definitions. *) Fixpoint simplify_step_WRONG (t:tm) {struct t} : option tm := match t with | t1 @ t2 => match simplify_step t1 with | None => match simplify_step t2 with | None => match t1 with | \ x, t12 => Some _ (({x |-> t2} t12)) | _ => None _ end | Some t2' => Some _ (t1 @ t2') end | Some t1' => Some _ (t1' @ t2) end | _ => None _ end. (* ---------------------------------------------------------------------- *) (* Multi-step evaluation *) (* As we did for previous languages, we can now define an [evalmany] relation that allows multiple steps of evaluation. But for purposes of experimenting with examples, it's more convenient to define a functional version first. The function [normalize] takes a term [t] and a number [steps] and performs up to [steps] single-step evaluations of [t] (using [simplify_step]) to try to reduce it to a normal form. If it succeeds in finding a normal form [t'] within [steps] steps, it returns [Some _ t']; otherwise it returns [None _]. *) Fixpoint normalize (t:tm) (steps:nat) {struct steps} : option tm := match steps with | O => None _ | S steps' => match simplify_step t with | None => Some _ t | Some t' => normalize t' steps' end end. (* For testing examples below, here is a convenient shorthand for the proposition "[t] normalizes to [t'] within 100 steps of evaluation." *) Notation " t -->* t' " := (normalize t (times ten ten) = Some _ t') (at level 80). (* ====================================================================== *) (* Programming in the untyped lambda-calculus *) Module LambdaExamples. (* To make the notation in the examples as close as possible to TAPL, let's introduce some more names for variables. *) Definition a : name := zero. Definition b : name := S a. Definition c : name := S b. Definition d : name := S c. Definition e : name := S d. Definition f : name := S e. Definition g : name := S f. Definition h : name := S g. Definition i : name := S h. Definition j : name := S i. Definition k : name := S j. Definition l : name := S k. Definition m : name := S l. Definition n : name := S m. Definition o : name := S n. Definition p : name := S o. Definition q : name := S p. Definition r : name := S q. Definition s : name := S r. Definition t : name := S s. Definition u : name := S t. Definition v : name := S u. Definition w : name := S v. Definition x : name := S w. Definition y : name := S x. Definition z : name := S y. (* ---------------------------------------------------------------------- *) (* Church booleans *) Notation tru := (\t, \f, t). Notation fls := (\t, \f, f). Lemma check_tru : tru @ AA @ BB -->* AA. Proof. reflexivity. Qed. Lemma check_fls : fls @ AA @ BB -->* BB. Proof. reflexivity. Qed. (* The lambda-term [bnot] takes a lambda-term [b] representing a boolean and yields another term representing the negation of this boolean. *) Notation bnot := (\b, b @ fls @ tru). Lemma check_bnot : ((bnot @ tru) @ AA @ BB) -->* BB. Proof. reflexivity. Qed. (* Similarly, [and] takes two arguments representing booleans and yields a lambda-term representing their logical conjunction. *) Notation and := (\b, \c, b @ c @ fls). Lemma check_and1 : ((and @ tru @ tru) @ AA @ BB) -->* AA. Proof. reflexivity. Qed. Lemma check_and2 : ((and @ tru @ fls) @ AA @ BB) -->* BB. Proof. reflexivity. Qed. Lemma check_and3 : ((and @ fls @ tru) @ AA @ BB) -->* BB. Proof. reflexivity. Qed. Lemma check_and4 : ((and @ fls @ fls) @ AA @ BB) -->* BB. Proof. reflexivity. Qed. (* EXERCISE: Write a lambda-term representing logical "or". *) (* <------ remove this comment Notation or := FILL IN HERE Lemma check_or1 : ((or @ tru @ tru) @ AA @ BB) -->* AA. Proof. reflexivity. Qed. Lemma check_or2 : ((or @ tru @ fls) @ AA @ BB) -->* AA. Proof. reflexivity. Qed. Lemma check_or3 : ((or @ fls @ tru) @ AA @ BB) -->* AA. Proof. reflexivity. Qed. Lemma check_or4 : ((or @ fls @ fls) @ AA @ BB) -->* BB. Proof. reflexivity. Qed. remove this comment ------> *) (* ---------------------------------------------------------------------- *) (* Pairs *) (* Using booleans, we can encode pairs of values as lambda-terms. *) Notation pair := (\f, \s, (\b, b @ f @ s)). Notation fst := (\p, p @ tru). Notation snd := (\p, p @ fls). Lemma check_pair1 : (fst @ (pair @ AA @ BB)) -->* AA. Proof. reflexivity. Qed. Lemma check_pair2 : (snd @ (pair @ AA @ BB)) -->* BB. Proof. reflexivity. Qed. (* EXERCISE: Define a similar set of functions for representing triples of values. *) (* <------ remove this comment Notation triple := (\f, \s, \t, FILL IN HERE Notation fst3 := FILL IN HERE Notation snd3 := FILL IN HERE Notation thd3 := FILL IN HERE Lemma check_fst3 : (fst3 @ (triple @ AA @ BB @ CC)) -->* AA. Proof. reflexivity. Qed. Lemma check_snd3 : (snd3 @ (triple @ AA @ BB @ CC)) -->* BB. Proof. reflexivity. Qed. Lemma check_thd3 : (thd3 @ (triple @ AA @ BB @ CC)) -->* CC. Proof. reflexivity. Qed. remove this comment ------> *) (* ---------------------------------------------------------------------- *) (* Church numerals *) (* Representing numbers as lambda-terms is also not too hard... *) Notation c_zero := (\s, \z, z). Notation c_one := (\s, \z, s @ z). Notation c_two := (\s, \z, s @ (s @ z)). Notation c_three := (\s, \z, s @ (s @ (s @ z))). Lemma check_three : (c_three @ AA @ BB) -->* AA @ (AA @ (AA @ BB)). Proof. reflexivity. Qed. Notation scc := (\n, \s, \z, s @ (n @ s @ z)). Lemma check_scc : ((scc @ (scc @ c_one)) @ AA @ BB) -->* AA @ (AA @ (AA @ BB)). Proof. reflexivity. Qed. Notation pls := (\m, \n, \s, \z, m @ s @ (n @ s @ z)). Lemma check_pls : ((pls @ c_one @ c_two) @ AA @ BB) -->* AA @ (AA @ (AA @ BB)). Proof. simpl. reflexivity. Qed. Notation tms := (\m, \n, m @ (pls @ n) @ c_zero). Lemma check_tms : ((tms @ c_two @ c_two) @ AA @ BB) -->* AA @ (AA @ (AA @ (AA @ BB))). Proof. simpl. reflexivity. Qed. (* EXERCISE: Is it possible to define [tms] without using [pls]? *) (* <------ remove this comment Notation tms' := (\m, \n, \s, \z, FILL IN HERE Lemma check_tms' : ((tms' @ c_two @ c_two) @ AA @ BB) -->* AA @ (AA @ (AA @ (AA @ BB))). Proof. simpl. reflexivity. Qed. remove this comment ------> *) Notation iszro := (\m, (\x, fls) @ tru). Notation zz := (pair @ c_zero @ c_zero). Notation ss := (\p, pair @ (snd @ p) @ (pls @ c_one @ (snd @ p))). Notation prd := (\m, fst @ (m @ ss @ zz)). Lemma check_prd : ((prd @ c_two) @ AA @ BB) -->* (AA @ BB). Proof. simpl. reflexivity. Qed. (* ---------------------------------------------------------------------- *) (* Divergence *) Notation omega := ((\x, x @ x) @ (\x, x @ x)). Lemma omega_not_normalizing : forall n, normalize omega n = None _. Proof. intros n0. induction n0. CASE "O". reflexivity. CASE "S". simpl. assumption. Qed. End LambdaExamples. End Lambda.