Require Import MyTactics. Require Import FixExtra. Require Import LambdaCalculusSyntax. Require Import LambdaCalculusValues. (* This is a definition of the CPS transformation. *) (* This CPS transformation is "one-pass" in the sense that it does not produce any administrative redexes. (In other words, there is no need for a second pass, whose purpose would be to remove administrative redexes.) To achieve this, instead of defining [cps t k], where the continuation [k] is a term, we define [cps t c], where the continuation [c] is either a term (also known as an object-level continuation) or a term-with-a-hole [K] (also known as a meta-level continuation). This formulation of the CPS transformation is analogous to Danvy and Filinski's higher-order formulation. Yet, it is still technically first-order, because we represent a term-with-a-hole [K] as a term, where the variable 0 denotes the hole. *) (* This CPS transformation is defined by recursion on the size of terms. This allows recursive calls of the form [cps (lift 1 t)], which would be illegal if [cps] was defined by structural induction. Definitions by well-founded recursion in Coq are somewhat tricky, requiring the use of the fixed point combinator [Fix] and the tactic [refine]. For explanations, see the chapter on general recursion in Chlipala's book at http://adam.chlipala.net/cpdt/html/GeneralRec.html *) (* The situation could be complicated by the fact that we wish to define two functions simultaneously: [cpsv v] is the translation of a value [v]. [cps t c] is the translation of a term [t] with continuation [c]. To avoid this problem, we follow Danvy and Filinski's approach, which is to first define [cps] directly (as this does not cause much duplication), then define [cpsv] in terms of [cps]. In the latter step, no case analysis is required: [cpsv] is obtained by invoking [cps] with an identity meta-level continuation. Regardless of how [cps] and [cpsv] are defined, we prove that the they satisfy the desired recurrence equations, so, in the end, everything is just as if they had been defined in a mutually recursive manner. *) (* -------------------------------------------------------------------------- *) (* As explained above, a continuation [c] is either [O k], where [k] is a term (in fact, a value) (an object-level continuation) or [M K], where [K] is term-with-a-hole (a meta-level continuation). A term-with-a-hole [K] is represented as a term where the variable 0 denotes the hole (and, of course, all other variables are shifted up). *) Inductive continuation := | O (k : term) | M (K : term). (* The term [apply c v] is the application of the continuation [c] to the value [v]. If [c] is an object-level continuation [k] (that is, a term), then it is just the object-level application [App k v]. If [c] is a meta-level continuation [K], then it is the meta-level operation of filling the hole with the value [v], which in fact is just a substitution, [K.[v/]]. *) Definition apply (c : continuation) (v : term) : term := match c with | O k => App k v | M K => K.[v/] end. (* The term [reify c] is the reification of the continuation [c] as an object-level continuation (that is, a term). If [c] is an object-level continuation [k], then it is just [k]. If [c] is a meta-level continuation [K], then [reify c] is the term [\x.K x]. (This is usually known as a two-level eta-expansion.) Because the hole is already represented by the variable 0, filling the hole with the variable [x] is a no-op. Therefore, it suffices to write [Lam K] to obtain the desired lambda-abstraction. *) Definition reify (c : continuation) : term := match c with | O k => k | M K => Lam K end. (* The continuation [substc sigma c] is the result of applying the substitution [sigma] to the continuation [c]. *) Definition substc sigma (c : continuation) : continuation := match c with | O k => O k.[sigma] | M K => M K.[up sigma] end. (* [liftc i c] is the result of lifting the free names of the continuation [c] up by [i]. The function [liftc] can be defined in terms of [substc]. *) Notation liftc i c := (substc (ren (+i)) c). (* -------------------------------------------------------------------------- *) (* Here is the definition of [cps]. Because we must keep track of sizes and prove that the recursive calls cause a decrease in the size, this definition cannot be easily written as Coq terms. Instead, we switch to proof mode and use the tactic [refine]. This allows us to write some of the code, with holes [_] in it, and use proof mode to fill the holes. *) (* [cps t c] is the CPS-translation of the term [t] with continuation [c]. *) Definition cps : term -> continuation -> term. Proof. (* The definition is by well-founded recursion on the size of [t]. *) refine (Fix smaller_wf_transparent (fun _ => continuation -> term) _). (* We receive the arguments [t] and [c] as well as a function [cps_] which we use for recursive calls. At every call to [cps_], there is an obligation to prove that the size of the argument is less than the size of [t]. *) intros t cps_ c. (* The definition is by cases on the term [t]. *) destruct t as [ x | t | t1 t2 | t1 t2 ]. (* When [t] is a value, we return an application of the continuation [c] to a value which will later be known as [cpsv t]. *) (* Case: [Var x]. *) { refine (apply c (Var x)). } (* Case: [Lam t]. *) (* In informal notation, the term [\x.t] is transformed to an application of [c] to [\x.\k.[cps t k]], where [k] is a fresh variable. Here, [k] is represented by the de Bruijn index 0, and the term [t] must be lifted because it is brought into the scope of [k]. *) { refine (apply c (Lam (* x *) (Lam (* k *) (cps_ (lift 1 t) _ (O (Var 0))))) ); abstract size. } (* Case: [App t1 t2]. *) (* The idea is, roughly, to first obtain the value [v1] of [t1], then obtain the value [v2] of [t2], then perform the application [v1 v2 c]. Two successive calls to [cps] are used to obtain [v1] and [v2]. In each case, we avoid administrative redexes by using an [M] continuation. Thus, [v1] and [v2] are represented by two holes, denoted by the variables [Var 1] and [Var 0]. If [t1] is a value, then the first hole will be filled directly with (the translation of) [t1]; otherwise, it will be filled with a fresh variable, bound to the result of evaluating (the translation of) [t1]. The same goes for [t2]. The application [v1 v2 c] does not directly make sense, since [c] is a continuation, not a term. Instead of [c], we must use [reify c]. The continuation [c] must turned into a term, so it can be used in this term-level application. A little de Bruijn wizardry is involved. The term [t2] must be lifted up by 1 because it is brought into the scope of the first meta-level continuation. Similarly, the first hole must be lifted by 1 because it is brought into the scope of the second meta-level continuation: thus, it becomes Var 1. Finally, the continuation [c] must be lifted up by [2] because it is brought into the scope of both. Here, we have a choice between [reify (liftc _ c)] and [lift _ (reify c)], which mean the same thing. *) { refine ( cps_ t1 _ (M ( cps_ (lift 1 t2) _ (M ( App (App (Var 1) (Var 0)) (lift 2 (reify c)) )) )) ); abstract size. } (* Case: [Let x = t1 in t2]. *) (* The idea is to first obtain the value [v1] of [t1]. This value is represented by the hole [Var 0] in the [M] continuation. We bind this value via a [Let] construct to the variable [x] (represented by the index 0 in [t2]), then execute [t2], under the original continuation [c]. All variables in [t2] except [x] must lifted up by one because they are brought in the scope of the meta-level continuation. The continuation [c] must be lifted up by 2 because it is brought in the scope of the meta-level continuation and in the scope of the [Let] construct. *) { refine ( cps_ t1 _ (M ( Let (Var 0) ( cps_ t2.[up (ren (+1))] _ (liftc 2 c) ) )) ); abstract size. } Defined. (* -------------------------------------------------------------------------- *) (* The above definition can be used inside Coq to compute the image of a term through the transformation. For instance, the image under [cps] of [\x.x] with object-level continuation [k] (a variable) is [k (\x.\k.k x)]. *) Goal cps (Lam (Var 0)) (O (Var 0)) = App (Var 0) (Lam (Lam (App (Var 0) (Var 1)))). Proof. compute. (* optional *) reflexivity. Qed. (* The image of [(\x.x) y] with continuation [k] is [(\x.\k.k x) y k]. *) Goal cps (App (Lam (Var 0)) (Var 0)) (O (Var 1)) = App (App (Lam (Lam (App (Var 0) (Var 1)))) (Var 0)) (Var 1). Proof. compute. (* optional *) reflexivity. Qed. (* -------------------------------------------------------------------------- *) (* The initial continuation is used when invoking [cps] at the top level. *) (* We could use [O (Lam (Var 0))] -- that is, the identity function -- as the initial continuation. Instead, we use [M (Var 0)], that is, the empty context. This sometimes saves one beta-redex. *) Definition init := M (Var 0). Definition cpsinit t := cps t init. (* -------------------------------------------------------------------------- *) (* Now that [cps] is defined, we can define [cpsv] in terms of it. *) (* We explicitly check whether [v] is a value, and if it is not, we return a dummy closed value. [cpsv] is supposed to be applied only to values, anyway. Using a dummy value allows us to prove that [cpsv v] is always a value, without requiring that [v] itself be a value. *) Definition cpsv (v : term) := if_value v (cpsinit v) (Lam (Var 0)). (* -------------------------------------------------------------------------- *) (* The function [cps] satisfies the expected recurrence equations. *) (* The lemmas [cps_var] and [cps_lam] are not used outside this file, as we use [cps_value] instead, followed with [cpsv_var] or [cpsv_lam]. *) Lemma cps_var: forall x c, cps (Var x) c = apply c (Var x). Proof. reflexivity. Qed. Lemma cps_lam: forall t c, cps (Lam t) c = apply c (Lam (* x *) (Lam (* k *) (cps (lift 1 t) (O (Var 0))))). Proof. intros. erewrite Fix_eq_simplified with (f := cps) by reflexivity. reflexivity. Qed. Lemma cps_app: forall t1 t2 c, cps (App t1 t2) c = cps t1 (M ( cps (lift 1 t2) (M ( App (App (Var 1) (Var 0)) (lift 2 (reify c)) )) )). Proof. intros. erewrite Fix_eq_simplified with (f := cps) by reflexivity. reflexivity. Qed. Lemma cps_let: forall t1 t2 c, cps (Let t1 t2) c = cps t1 (M ( Let (Var 0) ( cps t2.[up (ren (+1))] (liftc 2 c) ) )). Proof. intros. erewrite Fix_eq_simplified with (f := cps) by reflexivity. reflexivity. Qed. (* -------------------------------------------------------------------------- *) (* The translation of values is uniform: we also have the following equation. *) Lemma cps_value: forall v c, is_value v -> cps v c = apply c (cpsv v). Proof. destruct v; simpl; intros; try not_a_value; unfold cpsv, cpsinit. { rewrite !cps_var. reflexivity. } { rewrite !cps_lam. reflexivity. } Qed. (* -------------------------------------------------------------------------- *) (* The function [cpsv] satisfies the expected recurrence equations. *) Lemma cpsv_var: forall x, cpsv (Var x) = Var x. Proof. reflexivity. Qed. Lemma cpsv_lam: forall t, cpsv (Lam t) = Lam (Lam (cps (lift 1 t) (O (Var 0)))). Proof. intros. unfold cpsv, cpsinit. rewrite cps_lam. reflexivity. Qed. (* If [theta] is a renaming, then [theta x] is a variable, so [cpsv (theta x)] is [theta x], which can also be written [(Var x).[theta]]. *) Lemma cpsv_var_theta: forall theta x, is_ren theta -> cpsv (theta x) = (Var x).[theta]. Proof. inversion 1. subst. asimpl. reflexivity. Qed. (* -------------------------------------------------------------------------- *) (* The tactic [cps] applies the rewriting rules [cps_value] and [cps_app] as many times as possible, therefore expanding applications of the function [cps] to values and to applications. *) Ltac cps := repeat first [ rewrite cps_value by obvious | rewrite cps_app | rewrite cps_let ]. (* -------------------------------------------------------------------------- *) (* The translation of a value is a value. *) (* In fact, thanks to the manner in which we have defined [cpsv], the image of every term through [cpsv] is a value. This turns out to be quite pleasant, as it allows removing nasty side conditions in several lemmas. *) Lemma is_value_cpsv: forall v, (* is_value v -> *) is_value (cpsv v). Proof. intros. destruct v; simpl; tauto. Qed. Hint Resolve is_value_cpsv : is_value obvious. Hint Rewrite cpsv_var cpsv_lam : cpsv. Ltac cpsv := autorewrite with cpsv. (* -------------------------------------------------------------------------- *) (* As a final step, we prove an induction principle that helps work with the functions [cpsv] and [cps]. When trying to establish a property of the function [cps], we often need to prove this property by induction on the size of terms. Furthermore, we usually need to state an auxiliary property of the function [cpsv] and to prove the two statements simultaneously, by induction on the size of terms. The following lemma is tailored for this purpose. It proves the properties [Pcpsv] and [Pcps] simultaneously. The manner in which the index [n] is used reflects precisely the manner in which each function depends on the other, with or without a decrease in [n]. The dependencies are as follows: [cpsv] invokes [cps] with a size decrease. [cps] invokes [cpsv] without a size decrease and [cps] with a size decrease. It is worth noting that this proof has nothing to do with the definitions of [cpsv] and [cps]. It happens to reflect just the right dependencies between them. *) Lemma mutual_induction: forall (Pcpsv : term -> Prop) (Pcps : term -> continuation -> Prop), (forall n, (forall t c, size t < n -> Pcps t c) -> (forall v, size v < S n -> Pcpsv v) ) -> (forall n, (forall v, size v < S n -> Pcpsv v) -> (forall t c, size t < n -> Pcps t c) -> (forall t c, size t < S n -> Pcps t c) ) -> ( (forall v, Pcpsv v) /\ (forall t c, Pcps t c) ). Proof. intros Pcpsv Pcps IHcpsv IHcps. assert (fact: forall n, (forall v, size v < n -> Pcpsv v) /\ (forall t k, size t < n -> Pcps t k) ). { induction n; intros; split; intros; try solve [ elimtype False; omega ]; destruct IHn as (?&?); eauto. } split; intros; eapply fact; eauto. Qed. (* -------------------------------------------------------------------------- *) (* In the proofs that follow, we never expand the definition of [cpsv] or [cps]: we use the tactics [cpsv] and [cps] instead. We actually forbid unfolding [cpsv] and [cps], so our proofs cannot depend on the details of the above definitions. In general, when working with complex objects, it is good practice anyway to characterize an object and forget how it was defined. Here, the functions [cpsv] and [cps] are characterized by the equations that they satisfy; the rest does not matter. Attempting to work with transparent [cpsv] and [cps] would be problematic for several reasons. The tactics [simpl] and [asimpl] would sometimes expand these functions too far. Furthermore, because we have used the term [smaller_wf_transparent] inside the definition of [cps], expanding [cps] definition would often give rise to uncontrollably large terms. *) Global Opaque cps cpsv.