We demonstrate how to instantiate our framework to define the OGS associated to the CBV Ξ»-calculus. With the instance comes the proof that bisimilarity of the OGS entails substitution equivalence, which coincides with CIU-equivalence.
Note
This example is designed to be minimal, hiding by nature some difficulties. In particular it has no positive types, which simplifies the development a lot.
As discussed in the paper, our framework applies not really to a language but
more to an abstract machine. In order to ease this instanciation, we will
focus directly on a CBV machine and define evaluation contexts early on.
Working with intrinsically typed syntax, we need to give types to these
contexts: we will type them by the "formal negation" of the type of their
hole. In order to do so we first define the usual types ty0
of STLC with
functions and a ground type.
Declare Scope ty_scope . Inductive ty0 : Type := | ΞΉ : ty0 | Arr : ty0 -> ty0 -> ty0 . Notation "A β B" := (Arr A B) (at level 40) : ty_scope .
We then define "tagged types", where t+ a
will be the type of terms of type
a
, and t- a
the type of evaluation contexts of hole a
.
Variant ty : Type := | VTy : ty0 -> ty | KTy : ty0 -> ty . Notation "+ t" := (VTy t) (at level 5) : ty_scope . Notation "Β¬ t" := (KTy t) (at level 5) : ty_scope . Coercion VTy : ty0 >-> ty .
Typing contexts are now simply defined as lists of tagged types. This is perhaps the slightly surprising part: contexts will now contain "continuation variables". Rest assured terms will make no use of them. These are solely needed for evaluation contexts: as they are only typed with their hole, we are missing the type of their outside. We fix this problem by naming the outside and make evaluation contexts end with a continuation variable.
Note
Our choices make a bit more sense if we realize that what we are writing is exactly the subset of λμ-calculus that is the image of the CBV translation from λ-calculus.
We now have all that is needed to define terms, which we define mutually with values. As discussed above, they are are indexed by a list of tagged types (of which they only care about the non-negated elements) and by an untagged type.
The only fanciness is the recursive lambda abstraction, which we include to safeguard ourselves from accidentally using the fact that the language is strongly normalizing.
Inductive term (Ξ : t_ctx) : ty0 -> Type :=
| Val {a} : val Ξ a -> term Ξ a
| App {a b} : term Ξ (a β b) -> term Ξ a -> term Ξ b
with val (Ξ : t_ctx) : ty0 -> Type :=
| Var {a} : Ξ β + a -> val Ξ a
| TT : val Ξ ΞΉ
| Lam {a b} : term (Ξ βΆβ (a β b) βΆβ a) b -> val Ξ (a β b)
.
Evaluation contexts follow. As discussed, there is a "covariable" case, to "end" the context. The other cases are usual: evaluating the argument of an application and evaluating the function.
Inductive ev_ctx (Ξ : t_ctx) : ty0 -> Type :=
| K0 {a} : Ξ β Β¬ a -> ev_ctx Ξ a
| K1 {a b} : term Ξ (a β b) -> ev_ctx Ξ b -> ev_ctx Ξ a
| K2 {a b} : val Ξ a -> ev_ctx Ξ b -> ev_ctx Ξ (a β b)
.
Next are the machine states. They consist of an explicit cut and represent a term in the process of being executed in some context. Notice how states they are only indexed by a typing context: these are proper "diverging programs".
Definition state := term β₯β ev_ctx.
Finally the last of syntactic categories: "machine values". This category is typed with a tagged type and encompasses both values (for non-negated types) and evaluation contexts (for negated types). The primary use-case for this category is to denote "things by which we can substitute (tagged) variables".
In fact when working with intrinsically-typed syntax, substitution is modelled
as a monoidal multiplication (see [AACMM21] and [FS22]). We will prove later
that val_m Ξ
is indeed a monoid relative to has Ξ
and that contexts and
assignments form a category.
Equations val_m : t_ctx -> ty -> Type :=
val_m Ξ (+ a) := val Ξ a ;
val_m Ξ (Β¬ a) := ev_ctx Ξ a .
Together with machine values we define a smart constructor for "machine var",
embedding tagged variables into these generalized values. It conveniently
serves as the identity assignment, a fact we use to give it this mysterious
point-free style type, which desugars to forall a, Ξ β a -> val_m Ξ a
.
Equations a_id {Ξ} : Ξ =[val_m]> Ξ :=
a_id (+ _) i := Var i ;
a_id (Β¬ _) i := K0 i .
In order to define substitution we first need to dance the intrinsically-typed dance and define renamings, from which we will derive weakenings and then only define substitution.
Lets write intrinsically-typed parallel renaming for all of our syntactic categories! If you have never seen such intrinsically-typed definitions you might be surprised by the absence of error-prone de-bruijn index manipulation. Enjoy this beautiful syntax traversal!
Equations t_rename {Ξ Ξ} : Ξ β Ξ -> term Ξ βα΅’ term Ξ :=
t_rename f _ (Val v) := Val (v_rename f _ v) ;
t_rename f _ (App t1 t2) := App (t_rename f _ t1) (t_rename f _ t2) ;
with v_rename {Ξ Ξ} : Ξ β Ξ -> val Ξ βα΅’ val Ξ :=
v_rename f _ (Var i) := Var (f _ i) ;
v_rename f _ (TT) := TT ;
v_rename f _ (Lam t) := Lam (t_rename (r_shift2 f) _ t) .
Equations e_rename {Ξ Ξ} : Ξ β Ξ -> ev_ctx Ξ βα΅’ ev_ctx Ξ :=
e_rename f _ (K0 i) := K0 (f _ i) ;
e_rename f _ (K1 t Ο) := K1 (t_rename f _ t) (e_rename f _ Ο) ;
e_rename f _ (K2 v Ο) := K2 (v_rename f _ v) (e_rename f _ Ο) .
Equations s_rename {Ξ Ξ} : Ξ β Ξ -> state Ξ -> state Ξ :=
s_rename f (Cut t Ο) := Cut (t_rename f _ t) (e_rename f _ Ο).
Equations m_rename {Ξ Ξ} : Ξ β Ξ -> val_m Ξ βα΅’ val_m Ξ :=
m_rename f (+ _) v := v_rename f _ v ;
m_rename f (Β¬ _) Ο := e_rename f _ Ο .
#[global] Arguments s_rename _ _ _ /.
#[global] Arguments m_rename _ _ _ /.
We can recast m_rename
as an operator on assigments, more precisely as
renaming an assigment on the left.
Definition a_ren {Ξ1 Ξ2 Ξ3} : Ξ2 β Ξ3 -> Ξ1 =[val_m]> Ξ2 -> Ξ1 =[val_m]> Ξ3 :=
fun f g _ i => m_rename f _ (g _ i) .
The following bunch of notations will help us to keep the code readable:
Notation "t ββα΅£ r" := (t_rename r%asgn _ t) (at level 14).
Notation "v α΅₯βα΅£ r" := (v_rename r%asgn _ v) (at level 14).
Notation "Ο ββα΅£ r" := (e_rename r%asgn _ Ο) (at level 14).
Notation "v ββα΅£ r" := (m_rename r%asgn _ v) (at level 14).
Notation "s ββα΅£ r" := (s_rename r%asgn s) (at level 14).
Notation "a βα΅£ r" := (a_ren r%asgn a) (at level 14) : asgn_scope.
As discussed above, we can now obtain our precious weakenings. Here are the three we will need.
Definition t_shift {Ξ a} := @t_rename Ξ (Ξ βΆβ a) r_pop.
Definition m_shift2 {Ξ a b} := @m_rename Ξ (Ξ βΆβ a βΆβ b) (r_pop α΅£β r_pop).
Definition a_shift2 {Ξ Ξ a b} (f : Ξ =[val_m]> Ξ)
: (Ξ βΆβ a βΆβ b) =[val_m]> (Ξ βΆβ a βΆβ b)
:= [ [ a_map f m_shift2 ,β a_id a (pop top) ] ,β a_id b top ].
With weakenings in place we are now equipped to define substitutions. This goes pretty much like renaming. We have abstained from defining generic syntax traversal tools like Allais et al's "Kits" to keep our example minimal... And incidentally showcase the intrinsically-typed style with Matthieu Sozeau's Equations.
Equations t_subst {Ξ Ξ} : Ξ =[val_m]> Ξ -> term Ξ βα΅’ term Ξ :=
t_subst f _ (Val v) := Val (v_subst f _ v) ;
t_subst f _ (App t1 t2) := App (t_subst f _ t1) (t_subst f _ t2) ;
with v_subst {Ξ Ξ} : Ξ =[val_m]> Ξ -> val Ξ βα΅’ val Ξ :=
v_subst f _ (Var i) := f _ i ;
v_subst f _ (TT) := TT ;
v_subst f _ (Lam t) := Lam (t_subst (a_shift2 f) _ t) .
Equations e_subst {Ξ Ξ} : Ξ =[val_m]> Ξ -> ev_ctx Ξ βα΅’ ev_ctx Ξ :=
e_subst f _ (K0 i) := f _ i ;
e_subst f _ (K1 t Ο) := K1 (t_subst f _ t) (e_subst f _ Ο) ;
e_subst f _ (K2 v Ο) := K2 (v_subst f _ v) (e_subst f _ Ο) .
These notations will make everything shine.
Notation "t `ββ a" := (t_subst a%asgn _ t) (at level 30).
Notation "v `α΅₯β a" := (v_subst a%asgn _ v) (at level 30).
Notation "Ο `ββ a" := (e_subst a%asgn _ Ο) (at level 30).
These two last substitutions, for states and generalized values, are the ones we will give to the abstract interface. For categorical abstraction reasons, the abstract interface has the argument reversed for substitutions: first the state or value, then the assignment as second argument. To ease instanciation we will hence do so too. The type is given with tricky combinators but expands to:
m_subst Ξ a : val_m Ξ a -> forall Ξ, Ξ =[val_m]> Ξ -> val_m Ξ a
Equations m_subst : val_m ββ β¦ val_m , val_m β§β := m_subst _ (+ _) v _ f := v `α΅₯β f ; m_subst _ (Β¬ _) Ο _ f := Ο `ββ f . Definition s_subst : state ββ β¦ val_m , state β§β := fun _ s _ f => (s.(cut_l) `ββ f) β (s.(cut_r) `ββ f). #[global] Arguments s_subst _ _ /.
We can now instanciate the substitution monoid and module structures.
#[global] Instance val_m_monoid : subst_monoid val_m := {| v_var := @a_id ; v_sub := m_subst |} . #[global] Instance state_module : subst_module val_m state := {| c_sub := s_subst |} .
Finally we define a more usual substitution function which only substitutes the top two variables instead of doing parallel substitution.
Definition assign2 {Ξ a b} v1 v2
: (Ξ βΆβ a βΆβ b) =[val_m]> Ξ
:= [ [ a_id ,β v1 ] ,β v2 ] .
Definition t_subst2 {Ξ a b c} (t : term _ c) v1 v2 := t `ββ @assign2 Ξ a b v1 v2.
Notation "t /[ v1 , v2 ]" := (t_subst2 t v1 v2) (at level 50, left associativity).
As motivated earlier, the evaluator will be a defined as a state-machine, the
core definition being a state-transition function. To stick to the intrinsic
style, we wish that this state-machine stops only at evidently normal forms,
instead of stoping at states which happen to be in normal form. Perhaps more
simply said, we want to type the transition function as state Ξ β (state
Ξ + nf Ξ)
, where returning in the right component means stoping and outputing
a normal form.
In order to do this we first need to define normal forms! But instead of defining an inductive definition of normal forms as is customary, we will take an other route more tailored to OGS based on ultimate patterns.
As discussed in the paper, a central aspect of OGS is to circumvent the problem of naive trace semantics for higher-order languages by mean of a notion of "abstract values", or more commonly ultimate patterns, which define the "shareable part" of a value. For clarity reasons, instead of patterns we here take the dual point of view of "observations", which can be seen as patterns at the dual type (dual in the sense of swapping the tag). For our basic Ξ»-calculus, all types are negative -- that is, unsheareble -- hence the observations are pretty simple:
Observing a continuation of type Β¬ a
means returning a (hidden) value to
it. In terms of pattern this corresponds to the "fresh variable" pattern
Var x
.
Observing a function of type a β b
means giving it a hidden value and
a hidden continuation. In terms of patterns this corresponds to the
"application" co-pattern K2 (Var x) (K0 y)
.
Variant obs : ty -> Type :=
| ORet {a} : obs (Β¬ a)
| OApp {a b} : obs (a β b)
.
As observations correspond to a subset of (machine) values where all the
variables are "fresh", these variables have no counter-part in de-bruijn
notation (there is no meaningful notion of freshness). As such we have not
indexed obs
by any typing context, but to complete the definition we now
need to define a projection into typing contexts denoting the "domain",
"support" or more accurately "set of nameless fresh variables" of an
observation.
Equations obs_dom {a} : obs a -> t_ctx :=
obs_dom (@ORet a) := β
βΆβ + a ;
obs_dom (@OApp a b) := β
βΆβ + a βΆβ Β¬ b .
These two definitions together form an operator.
Definition obs_op : Oper ty t_ctx := {| o_op := obs ; o_dom := @obs_dom |} . Notation ORet' := (ORet : o_op obs_op _). Notation OApp' := (OApp : o_op obs_op _).
Given a value, an observation on its type and a value for each fresh variable of the observation, we can "refold" everything and form a new state which will indeed observe this value.
Equations obs_app {Ξ a} (v : val_m Ξ a) (p : obs a) (Ξ³ : obs_dom p =[val_m]> Ξ) : state Ξ :=
obs_app v OApp Ξ³ := Val v β
K2 (Ξ³ _ (pop top)) (Ξ³ _ top) ;
obs_app Ο ORet Ξ³ := Val (Ξ³ _ top) β
(Ο : ev_ctx _ _) .
Normal forms for CBV Ξ»-calculus can be characterized as either a value v
or
a stuck application in evaluation context E[x v]
(see "eager-normal forms"
in [L05]). Now it doesn't take much squinting to see that in our setting,
this corresponds respectively to states of the form β¨ v | K0 x β©
and β¨ Var
x | K2 v Ο β©
. Squinting a bit more, we can reap the benefits of our unified
treatment of terms and contexts and see that both of these cases work in the
same way: normal states are states given by a variable facing an observation
whose fresh variables have been substituted with values.
Having already defined observation and their set of fresh variables, an
observation stuffed with values in typing context Ξ
can be represented
simply by a formal substitution of an observation o
and an assigment
obs_dom o =[val]> Ξ
. This "split" definition of normal forms is the one we
will take.
Definition nf := c_var β₯β (obs_op # val_m).
Everything is now in place to define our state transition function. The reduction rules should come to no surprise:
β¨ t1 t2 | Ο β© β β¨ t2 | t1 β
1 Ο β©
β¨ v | x β©
normalβ¨ v | t β
1 Ο β© β β¨ t | v β
2 Ο β©
β¨ x | v β
2 Ο β©
normalβ¨ Ξ»fx.t | v β
2 Ο β© β β¨ t[fβ¦Ξ»fx.t; xβ¦v] | Ο β©
Rules 1,3,5 step to a new configuration, while cases 2,4 are stuck on normal forms.
Equations eval_step {Ξ : t_ctx} : state Ξ -> sum (state Ξ) (nf Ξ) :=
eval_step (Cut (App t1 t2) (Ο)) := inl (Cut t2 (K1 t1 Ο)) ;
eval_step (Cut (Val v) (K0 i)) := inr (i β
ORet' β¦ [ ! ,β v ] β¦) ;
eval_step (Cut (Val v) (K1 t Ο)) := inl (Cut t (K2 v Ο)) ;
eval_step (Cut (Val (Var i)) (K2 v Ο)) := inr (i β
OApp' β¦ [ [ ! ,β v ] ,β Ο ] β¦) ;
eval_step (Cut (Val (Lam t)) (K2 v Ο)) := inl (Cut (t /[ Lam t , v ]) Ο) .
Having defined the transition function, we can now iterate it inside the delay monad. This constructs a possibly non-terminating computation ending with a normal form.
Definition stlc_eval {Ξ : t_ctx} : state Ξ -> delay (nf Ξ)
:= iter_delay (ret_delay β eval_step).
We have now finished all the computational parts of the instanciation, but all the proofs are left to be done. Before attacking the OGS-specific hypotheses, we will need to prove the usual standard lemmata on renaming and substitution.
There will be a stack of lemmata which will all pretty much be simple inductions on the syntax, so we start by introducing some helpers for this. In fact it is not completely direct to do since terms and values are mutually defined: we will need to derive a mutual induction principle.
Scheme term_mut := Induction for term Sort Prop
with val_mut := Induction for val Sort Prop .
Annoyingly, Coq treats this mutual induction principle as two separate
induction principles. They both have the exact same premises but differ in
their conclusion. Thus we define a datatype for these premises, to avoid
duplicating the proofs. Additionally, evaluation contexts are not defined
mutually with terms and values, but it doesn't hurt to prove their properties
simultaneously too, so syn_ind_args
is in fact closer to the premises of
a three-way mutual induction principle between terms, values and evaluation
contexts.
Record syn_ind_args (P_t : forall Ξ A, term Ξ A -> Prop) (P_v : forall Ξ A, val Ξ A -> Prop) (P_e : forall Ξ A, ev_ctx Ξ A -> Prop) := { ind_val {Ξ a} v (_ : P_v Ξ a v) : P_t Ξ a (Val v) ; ind_app {Ξ a b} t1 (_ : P_t Ξ (a β b) t1) t2 (_ : P_t Ξ a t2) : P_t Ξ b (App t1 t2) ; ind_var {Ξ a} i : P_v Ξ a (Var i) ; ind_tt {Ξ} : P_v Ξ (ΞΉ) (TT) ; ind_lamrec {Ξ a b} t (_ : P_t _ b t) : P_v Ξ (a β b) (Lam t) ; ind_kvar {Ξ a} i : P_e Ξ a (K0 i) ; ind_kfun {Ξ a b} t (_ : P_t Ξ (a β b) t) Ο (_ : P_e Ξ b Ο) : P_e Ξ a (K1 t Ο) ; ind_karg {Ξ a b} v (_ : P_v Ξ a v) Ο (_ : P_e Ξ b Ο) : P_e Ξ (a β b) (K2 v Ο) } . Lemma term_ind_mut P_t P_v P_e (H : syn_ind_args P_t P_v P_e) Ξ a t : P_t Ξ a t . destruct H; now apply (term_mut P_t P_v). Qed. Lemma val_ind_mut P_t P_v P_e (H : syn_ind_args P_t P_v P_e) Ξ a v : P_v Ξ a v . destruct H; now apply (val_mut P_t P_v). Qed. Lemma ctx_ind_mut P_t P_v P_e (H : syn_ind_args P_t P_v P_e) Ξ a Ο : P_e Ξ a Ο . induction Ο. - apply (ind_kvar _ _ _ H). - apply (ind_kfun _ _ _ H); auto; apply (term_ind_mut _ _ _ H). - apply (ind_karg _ _ _ H); auto; apply (val_ind_mut _ _ _ H). Qed.
Now equipped we can start with the first lemma: renaming respects pointwise equality of assignments. As discussed, we will prove this by mutual induction on our three "base" syntactic categories of terms, values and evaluation contexts, and then we will also deduce it for the three "derived" notions of machine values, states and assigments. Sometimes some of the derived notions will be omitted if it is not needed later on.
This proof, like all the following ones will follow a simple pattern: a simplification; an application of congruence; a fixup for the two-time shifted assigment in the case of Ξ»; finally a call to the induction hypothesis.
Note
Here is definitely where the generic syntax traversal kit of Guillaume Allais et al would shine. Indeed the proof pattern i outlined can really be formalized into a generic proof.
Definition t_ren_proper_P Ξ a (t : term Ξ a) : Prop :=
forall Ξ (f1 f2 : Ξ β Ξ), f1 β‘β f2 -> t ββα΅£ f1 = t ββα΅£ f2 .
Definition v_ren_proper_P Ξ a (v : val Ξ a) : Prop :=
forall Ξ (f1 f2 : Ξ β Ξ), f1 β‘β f2 -> v α΅₯βα΅£ f1 = v α΅₯βα΅£ f2 .
Definition e_ren_proper_P Ξ a (Ο : ev_ctx Ξ a) : Prop :=
forall Ξ (f1 f2 : Ξ β Ξ), f1 β‘β f2 -> Ο ββα΅£ f1 = Ο ββα΅£ f2 .
Lemma ren_proper_prf : syn_ind_args t_ren_proper_P v_ren_proper_P e_ren_proper_P.
econstructor.
all: unfold t_ren_proper_P, v_ren_proper_P, e_ren_proper_P.
all: intros; cbn; f_equal; eauto.
all: eapply H; now apply r_shift2_eq.
Qed.
#[global] Instance t_ren_eq {Ξ Ξ}
: Proper (asgn_eq _ _ ==> forall_relation (fun a => eq ==> eq)) (@t_rename Ξ Ξ).
intros f1 f2 H1 a x y ->; now apply (term_ind_mut _ _ _ ren_proper_prf).
Qed.
#[global] Instance v_ren_eq {Ξ Ξ}
: Proper (asgn_eq _ _ ==> forall_relation (fun a => eq ==> eq)) (@v_rename Ξ Ξ).
intros f1 f2 H1 a x y ->; now apply (val_ind_mut _ _ _ ren_proper_prf).
Qed.
#[global] Instance e_ren_eq {Ξ Ξ}
: Proper (asgn_eq _ _ ==> forall_relation (fun a => eq ==> eq)) (@e_rename Ξ Ξ).
intros f1 f2 H1 a x y ->; now apply (ctx_ind_mut _ _ _ ren_proper_prf).
Qed.
#[global] Instance m_ren_eq {Ξ Ξ}
: Proper (asgn_eq _ _ ==> forall_relation (fun a => eq ==> eq)) (@m_rename Ξ Ξ).
intros ? ? H1 [] _ ? ->; cbn in *; now rewrite H1.
Qed.
#[global] Instance s_ren_eq {Ξ Ξ}
: Proper (asgn_eq _ _ ==> eq ==> eq) (@s_rename Ξ Ξ).
intros ? ? H1 _ x ->; destruct x; unfold s_rename; cbn; now rewrite H1.
Qed.
#[global] Instance a_ren_eq {Ξ1 Ξ2 Ξ3}
: Proper (asgn_eq _ _ ==> asgn_eq _ _ ==> asgn_eq _ _) (@a_ren Ξ1 Ξ2 Ξ3).
intros ? ? H1 ? ? H2 ? ?; apply (m_ren_eq _ _ H1), H2.
Qed.
#[global] Instance a_shift2_eq {Ξ Ξ x y} : Proper (asgn_eq _ _ ==> asgn_eq _ _) (@a_shift2 Ξ Ξ x y).
intros ? ? H ? v.
do 2 (dependent elimination v; cbn; auto).
now rewrite H.
Qed.
Lemma 2: renaming-renaming assocativity. I say "associativity" because it definitely looks like associativity if we disregard the subscripts. More precisely it could be described as the composition law a right action.
Definition t_ren_ren_P Ξ1 a (t : term Ξ1 a) : Prop :=
forall Ξ2 Ξ3 (f1 : Ξ1 β Ξ2) (f2 : Ξ2 β Ξ3),
(t ββα΅£ f1) ββα΅£ f2 = t ββα΅£ (f1 α΅£β f2).
Definition v_ren_ren_P Ξ1 a (v : val Ξ1 a) : Prop :=
forall Ξ2 Ξ3 (f1 : Ξ1 β Ξ2) (f2 : Ξ2 β Ξ3),
(v α΅₯βα΅£ f1) α΅₯βα΅£ f2 = v α΅₯βα΅£ (f1 α΅£β f2).
Definition e_ren_ren_P Ξ1 a (Ο : ev_ctx Ξ1 a) : Prop :=
forall Ξ2 Ξ3 (f1 : Ξ1 β Ξ2) (f2 : Ξ2 β Ξ3),
(Ο ββα΅£ f1) ββα΅£ f2 = Ο ββα΅£ (f1 α΅£β f2).
Lemma ren_ren_prf : syn_ind_args t_ren_ren_P v_ren_ren_P e_ren_ren_P .
econstructor.
all: unfold t_ren_ren_P, v_ren_ren_P, e_ren_ren_P.
all: intros; cbn; f_equal; auto.
rewrite H; apply t_ren_eq; auto.
intros ? v; now do 2 (dependent elimination v; eauto).
Qed.
Lemma t_ren_ren {Ξ1 Ξ2 Ξ3} (f1 : Ξ1 β Ξ2) (f2 : Ξ2 β Ξ3) a (t : term Ξ1 a)
: (t ββα΅£ f1) ββα΅£ f2 = t ββα΅£ (f1 α΅£β f2).
now apply (term_ind_mut _ _ _ ren_ren_prf).
Qed.
Lemma v_ren_ren {Ξ1 Ξ2 Ξ3} (f1 : Ξ1 β Ξ2) (f2 : Ξ2 β Ξ3) a (v : val Ξ1 a)
: (v α΅₯βα΅£ f1) α΅₯βα΅£ f2 = v α΅₯βα΅£ (f1 α΅£β f2).
now apply (val_ind_mut _ _ _ ren_ren_prf).
Qed.
Lemma e_ren_ren {Ξ1 Ξ2 Ξ3} (f1 : Ξ1 β Ξ2) (f2 : Ξ2 β Ξ3) a (Ο : ev_ctx Ξ1 a)
: (Ο ββα΅£ f1) ββα΅£ f2 = Ο ββα΅£ (f1 α΅£β f2).
now apply (ctx_ind_mut _ _ _ ren_ren_prf).
Qed.
Lemma m_ren_ren {Ξ1 Ξ2 Ξ3} (f1 : Ξ1 β Ξ2) (f2 : Ξ2 β Ξ3) a (v : val_m Ξ1 a)
: (v ββα΅£ f1) ββα΅£ f2 = v ββα΅£ (f1 α΅£β f2).
destruct a; [ now apply v_ren_ren | now apply e_ren_ren ].
Qed.
Lemma s_ren_ren {Ξ1 Ξ2 Ξ3} (f1 : Ξ1 β Ξ2) (f2 : Ξ2 β Ξ3) (s : state Ξ1)
: (s ββα΅£ f1) ββα΅£ f2 = s ββα΅£ (f1 α΅£β f2).
destruct s; apply (f_equal2 Cut); [ now apply t_ren_ren | now apply e_ren_ren ].
Qed.
Lemma 3: left identity law of renaming.
Definition t_ren_id_l_P Ξ a (t : term Ξ a) : Prop := t ββα΅£ r_id = t .
Definition v_ren_id_l_P Ξ a (v : val Ξ a) : Prop := v α΅₯βα΅£ r_id = v .
Definition e_ren_id_l_P Ξ a (Ο : ev_ctx Ξ a) : Prop := Ο ββα΅£ r_id = Ο.
Lemma ren_id_l_prf : syn_ind_args t_ren_id_l_P v_ren_id_l_P e_ren_id_l_P .
econstructor.
all: unfold t_ren_id_l_P, v_ren_id_l_P, e_ren_id_l_P.
all: intros; cbn; f_equal; auto.
rewrite <- H at 2; apply t_ren_eq; auto.
intros ? v; now do 2 (dependent elimination v; eauto).
Qed.
Lemma t_ren_id_l {Ξ} a (t : term Ξ a) : t ββα΅£ r_id = t .
now apply (term_ind_mut _ _ _ ren_id_l_prf).
Qed.
Lemma v_ren_id_l {Ξ} a (v : val Ξ a) : v α΅₯βα΅£ r_id = v.
now apply (val_ind_mut _ _ _ ren_id_l_prf).
Qed.
Lemma e_ren_id_l {Ξ} a (Ο : ev_ctx Ξ a) : Ο ββα΅£ r_id = Ο .
now apply (ctx_ind_mut _ _ _ ren_id_l_prf).
Qed.
Lemma m_ren_id_l {Ξ} a (v : val_m Ξ a) : v ββα΅£ r_id = v .
destruct a; [ now apply v_ren_id_l | now apply e_ren_id_l ].
Qed.
Lemma s_ren_id_l {Ξ} (s : state Ξ) : s ββα΅£ r_id = s.
destruct s; apply (f_equal2 Cut); [ now apply t_ren_id_l | now apply e_ren_id_l ].
Qed.
Lemma 4: right identity law of renaming. This one basically holds definitionally, it only needs a case split for some of the derived notions. We will also prove a consequence on weakenings: identity law.
Lemma m_ren_id_r {Ξ Ξ} (f : Ξ β Ξ) {a} (i : Ξ β a) : a_id _ i ββα΅£ f = a_id _ (f _ i) .
now destruct a.
Qed.
Lemma a_ren_id_r {Ξ Ξ} (f : Ξ β Ξ) : a_id βα΅£ f β‘β f α΅£β a_id .
intros ??; now apply m_ren_id_r.
Qed.
Lemma a_shift2_id {Ξ x y} : @a_shift2 Ξ Ξ x y a_id β‘β a_id.
intros ? v; do 2 (dependent elimination v; auto).
exact (m_ren_id_r _ _).
Qed.
Lemma 5: shifting assigments commutes with left and right renaming.
Lemma a_shift2_s_ren {Ξ1 Ξ2 Ξ3 a b} (f1 : Ξ1 β Ξ2) (f2 : Ξ2 =[val_m]> Ξ3)
: @a_shift2 _ _ a b (f1 α΅£β f2) β‘β r_shift2 f1 α΅£β a_shift2 f2 .
intros ? v; do 2 (dependent elimination v; auto).
Qed.
Lemma a_shift2_a_ren {Ξ1 Ξ2 Ξ3 a b} (f1 : Ξ1 =[val_m]> Ξ2) (f2 : Ξ2 β Ξ3)
: @a_shift2 _ _ a b (f1 βα΅£ f2) β‘β a_shift2 f1 βα΅£ r_shift2 f2 .
intros ? v.
do 2 (dependent elimination v; cbn; [ symmetry; exact (a_ren_id_r _ _ _) | ]).
unfold a_ren; cbn - [ m_rename ]; unfold m_shift2; now rewrite 2 m_ren_ren.
Qed.
Lemma 6: substitution respects pointwise equality of assigments.
Definition t_sub_proper_P Ξ a (t : term Ξ a) : Prop :=
forall Ξ (f1 f2 : Ξ =[val_m]> Ξ), f1 β‘β f2 -> t `ββ f1 = t `ββ f2 .
Definition v_sub_proper_P Ξ a (v : val Ξ a) : Prop :=
forall Ξ (f1 f2 : Ξ =[val_m]> Ξ), f1 β‘β f2 -> v `α΅₯β f1 = v `α΅₯β f2 .
Definition e_sub_proper_P Ξ a (Ο : ev_ctx Ξ a) : Prop :=
forall Ξ (f1 f2 : Ξ =[val_m]> Ξ), f1 β‘β f2 -> Ο `ββ f1 = Ο `ββ f2.
Lemma sub_proper_prf : syn_ind_args t_sub_proper_P v_sub_proper_P e_sub_proper_P.
econstructor.
all: unfold t_sub_proper_P, v_sub_proper_P, e_sub_proper_P.
all: intros; cbn; f_equal; auto.
now apply H, a_shift2_eq.
Qed.
#[global] Instance t_sub_eq {Ξ Ξ}
: Proper (asgn_eq _ _ ==> forall_relation (fun a => eq ==> eq)) (@t_subst Ξ Ξ).
intros ? ? H1 a _ ? ->; now apply (term_ind_mut _ _ _ sub_proper_prf).
Qed.
#[global] Instance v_sub_eq {Ξ Ξ}
: Proper (asgn_eq _ _ ==> forall_relation (fun a => eq ==> eq)) (@v_subst Ξ Ξ).
intros ? ? H1 a _ ? ->; now apply (val_ind_mut _ _ _ sub_proper_prf).
Qed.
#[global] Instance e_sub_eq {Ξ Ξ}
: Proper (asgn_eq _ _ ==> forall_relation (fun a => eq ==> eq)) (@e_subst Ξ Ξ).
intros ? ? H1 a _ ? ->; now apply (ctx_ind_mut _ _ _ sub_proper_prf).
Qed.
#[global] Instance m_sub_eq
: Proper (ββ Ξ, ββ _, eq ==> ββ Ξ, asgn_eq Ξ Ξ ==> eq) m_subst.
intros ? [] ?? -> ??? H1; cbn in *; now rewrite H1.
Qed.
#[global] Instance s_sub_eq
: Proper (ββ Ξ, eq ==> ββ Ξ, asgn_eq Ξ Ξ ==> eq) s_subst.
intros ??[] -> ??? H1; cbn; now rewrite H1.
Qed.
(*
#[global] Instance a_comp_eq {Ξ1 Ξ2 Ξ3} : Proper (ass_eq _ _ ==> ass_eq _ _ ==> ass_eq _ _) (@a_comp Ξ1 Ξ2 Ξ3).
intros ? ? H1 ? ? H2 ? ?; unfold a_comp, s_map; now rewrite H1, H2.
Qed.
*)
Lemma 7: renaming-substitution "associativity".
Definition t_ren_sub_P Ξ1 a (t : term Ξ1 a) : Prop :=
forall Ξ2 Ξ3 (f1 : Ξ1 =[val_m]> Ξ2) (f2 : Ξ2 β Ξ3) ,
(t `ββ f1) ββα΅£ f2 = t `ββ (f1 βα΅£ f2) .
Definition v_ren_sub_P Ξ1 a (v : val Ξ1 a) : Prop :=
forall Ξ2 Ξ3 (f1 : Ξ1 =[val_m]> Ξ2) (f2 : Ξ2 β Ξ3) ,
(v `α΅₯β f1) α΅₯βα΅£ f2 = v `α΅₯β (f1 βα΅£ f2) .
Definition e_ren_sub_P Ξ1 a (Ο : ev_ctx Ξ1 a) : Prop :=
forall Ξ2 Ξ3 (f1 : Ξ1 =[val_m]> Ξ2) (f2 : Ξ2 β Ξ3) ,
(Ο `ββ f1) ββα΅£ f2 = Ο `ββ (f1 βα΅£ f2) .
Lemma ren_sub_prf : syn_ind_args t_ren_sub_P v_ren_sub_P e_ren_sub_P.
econstructor.
all: unfold t_ren_sub_P, v_ren_sub_P, e_ren_sub_P.
all: intros; cbn; f_equal.
all: try rewrite a_shift2_a_ren; auto.
Qed.
Lemma t_ren_sub {Ξ1 Ξ2 Ξ3} (f1 : Ξ1 =[val_m]> Ξ2) (f2 : Ξ2 β Ξ3) a (t : term Ξ1 a)
: (t `ββ f1) ββα΅£ f2 = t `ββ (f1 βα΅£ f2) .
now apply (term_ind_mut _ _ _ ren_sub_prf).
Qed.
Lemma v_ren_sub {Ξ1 Ξ2 Ξ3} (f1 : Ξ1 =[val_m]> Ξ2) (f2 : Ξ2 β Ξ3) a (v : val Ξ1 a)
: (v `α΅₯β f1) α΅₯βα΅£ f2 = v `α΅₯β (f1 βα΅£ f2) .
now apply (val_ind_mut _ _ _ ren_sub_prf).
Qed.
Lemma e_ren_sub {Ξ1 Ξ2 Ξ3} (f1 : Ξ1 =[val_m]> Ξ2) (f2 : Ξ2 β Ξ3) a (Ο : ev_ctx Ξ1 a)
: (Ο `ββ f1) ββα΅£ f2 = Ο `ββ (f1 βα΅£ f2) .
now apply (ctx_ind_mut _ _ _ ren_sub_prf).
Qed.
Lemma m_ren_sub {Ξ1 Ξ2 Ξ3} (f1 : Ξ1 =[val_m]> Ξ2) (f2 : Ξ2 β Ξ3) a (v : val_m Ξ1 a)
: (v α΅₯β f1) ββα΅£ f2 = v α΅₯β (f1 βα΅£ f2) .
destruct a; [ now apply v_ren_sub | now apply e_ren_sub ].
Qed.
Lemma s_ren_sub {Ξ1 Ξ2 Ξ3} (f1 : Ξ1 =[val_m]> Ξ2) (f2 : Ξ2 β Ξ3) (s : state Ξ1)
: (s ββ f1) ββα΅£ f2 = s ββ (f1 βα΅£ f2) .
destruct s; cbn; now rewrite t_ren_sub, e_ren_sub.
Qed.
Lemma 8: substitution-renaming "associativity".
Definition t_sub_ren_P Ξ1 a (t : term Ξ1 a) : Prop :=
forall Ξ2 Ξ3 (f1 : Ξ1 β Ξ2) (f2 : Ξ2 =[val_m]> Ξ3) ,
(t ββα΅£ f1) `ββ f2 = t `ββ (f1 α΅£β f2) .
Definition v_sub_ren_P Ξ1 a (v : val Ξ1 a) : Prop :=
forall Ξ2 Ξ3 (f1 : Ξ1 β Ξ2) (f2 : Ξ2 =[val_m]> Ξ3) ,
(v α΅₯βα΅£ f1) `α΅₯β f2 = v `α΅₯β (f1 α΅£β f2) .
Definition e_sub_ren_P Ξ1 a (Ο : ev_ctx Ξ1 a) : Prop :=
forall Ξ2 Ξ3 (f1 : Ξ1 β Ξ2) (f2 : Ξ2 =[val_m]> Ξ3) ,
(Ο ββα΅£ f1) `ββ f2 = Ο `ββ (f1 α΅£β f2) .
Lemma sub_ren_prf : syn_ind_args t_sub_ren_P v_sub_ren_P e_sub_ren_P.
econstructor.
all: unfold t_sub_ren_P, v_sub_ren_P, e_sub_ren_P.
all: intros; cbn; f_equal; auto.
now rewrite a_shift2_s_ren.
Qed.
Lemma t_sub_ren {Ξ1 Ξ2 Ξ3} (f1 : Ξ1 β Ξ2) (f2 : Ξ2 =[val_m]> Ξ3) a (t : term Ξ1 a)
: (t ββα΅£ f1) `ββ f2 = t `ββ (f1 α΅£β f2) .
now apply (term_ind_mut _ _ _ sub_ren_prf).
Qed.
Lemma v_sub_ren {Ξ1 Ξ2 Ξ3} (f1 : Ξ1 β Ξ2) (f2 : Ξ2 =[val_m]> Ξ3) a (v : val Ξ1 a)
: (v α΅₯βα΅£ f1) `α΅₯β f2 = v `α΅₯β (f1 α΅£β f2) .
now apply (val_ind_mut _ _ _ sub_ren_prf).
Qed.
Lemma e_sub_ren {Ξ1 Ξ2 Ξ3} (f1 : Ξ1 β Ξ2) (f2 : Ξ2 =[val_m]> Ξ3) a (Ο : ev_ctx Ξ1 a)
: (Ο ββα΅£ f1) `ββ f2 = Ο `ββ (f1 α΅£β f2) .
now apply (ctx_ind_mut _ _ _ sub_ren_prf).
Qed.
Lemma m_sub_ren {Ξ1 Ξ2 Ξ3} (f1 : Ξ1 β Ξ2) (f2 : Ξ2 =[val_m]> Ξ3) a (v : val_m Ξ1 a)
: (v ββα΅£ f1) α΅₯β f2 = v α΅₯β (f1 α΅£β f2) .
destruct a; [ now apply v_sub_ren | now apply e_sub_ren ].
Qed.
Lemma s_sub_ren {Ξ1 Ξ2 Ξ3} (f1 : Ξ1 β Ξ2) (f2 : Ξ2 =[val_m]> Ξ3) (s : state Ξ1)
: (s ββα΅£ f1) ββ f2 = s ββ (f1 α΅£β f2) .
destruct s; cbn; now rewrite t_sub_ren, e_sub_ren.
Qed.
Lemma 9: left identity law of substitution.
Definition t_sub_id_l_P Ξ a (t : term Ξ a) : Prop := t `ββ a_id = t .
Definition v_sub_id_l_P Ξ a (v : val Ξ a) : Prop := v `α΅₯β a_id = v .
Definition e_sub_id_l_P Ξ a (Ο : ev_ctx Ξ a) : Prop := Ο `ββ a_id = Ο .
Lemma sub_id_l_prf : syn_ind_args t_sub_id_l_P v_sub_id_l_P e_sub_id_l_P.
econstructor.
all: unfold t_sub_id_l_P, v_sub_id_l_P, e_sub_id_l_P.
all: intros; cbn; f_equal; auto.
now rewrite a_shift2_id.
Qed.
Lemma t_sub_id_l {Ξ} a (t : term Ξ a) : t `ββ a_id = t .
now apply (term_ind_mut _ _ _ sub_id_l_prf).
Qed.
Lemma v_sub_id_l {Ξ} a (v : val Ξ a) : v `α΅₯β a_id = v .
now apply (val_ind_mut _ _ _ sub_id_l_prf).
Qed.
Lemma e_sub_id_l {Ξ} a (Ο : ev_ctx Ξ a) : Ο `ββ a_id = Ο .
now apply (ctx_ind_mut _ _ _ sub_id_l_prf).
Qed.
Lemma m_sub_id_l {Ξ} a (v : val_m Ξ a) : v α΅₯β a_id = v .
destruct a; [ now apply v_sub_id_l | now apply e_sub_id_l ].
Qed.
Lemma s_sub_id_l {Ξ} (s : state Ξ) : s ββ a_id = s .
destruct s; cbn; now rewrite t_sub_id_l, e_sub_id_l.
Qed.
Lemma 9: right identity law of substitution. As for renaming, this one is mostly by definition.
Lemma m_sub_id_r {Ξ1 Ξ2} {a} (i : Ξ1 β a) (f : Ξ1 =[val_m]> Ξ2) : a_id a i α΅₯β f = f a i.
now destruct a.
Qed.
Lemma 10: shifting assigments respects composition.
Lemma a_shift2_comp {Ξ1 Ξ2 Ξ3 a b} (f1 : Ξ1 =[val_m]> Ξ2) (f2 : Ξ2 =[val_m]> Ξ3)
: @a_shift2 _ _ a b (f1 β f2) β‘β a_shift2 f1 β a_shift2 f2 .
intros ? v.
do 2 (dependent elimination v; [ symmetry; exact (m_sub_id_r _ _) | ]).
cbn; unfold m_shift2; now rewrite m_ren_sub, m_sub_ren.
Qed.
Lemma 11: substitution-substitution associativity, ie composition law.
Definition t_sub_sub_P Ξ1 a (t : term Ξ1 a) : Prop :=
forall Ξ2 Ξ3 (f1 : Ξ1 =[val_m]> Ξ2) (f2 : Ξ2 =[val_m]> Ξ3) ,
t `ββ (f1 β f2) = (t `ββ f1) `ββ f2 .
Definition v_sub_sub_P Ξ1 a (v : val Ξ1 a) : Prop :=
forall Ξ2 Ξ3 (f1 : Ξ1 =[val_m]> Ξ2) (f2 : Ξ2 =[val_m]> Ξ3) ,
v `α΅₯β (f1 β f2) = (v `α΅₯β f1) `α΅₯β f2 .
Definition e_sub_sub_P Ξ1 a (Ο : ev_ctx Ξ1 a) : Prop :=
forall Ξ2 Ξ3 (f1 : Ξ1 =[val_m]> Ξ2) (f2 : Ξ2 =[val_m]> Ξ3) ,
Ο `ββ (f1 β f2) = (Ο `ββ f1) `ββ f2 .
Lemma sub_sub_prf : syn_ind_args t_sub_sub_P v_sub_sub_P e_sub_sub_P.
econstructor.
all: unfold t_sub_sub_P, v_sub_sub_P, e_sub_sub_P.
all: intros; cbn; f_equal; auto.
now rewrite a_shift2_comp.
Qed.
Lemma t_sub_sub {Ξ1 Ξ2 Ξ3} (f1 : Ξ1 =[val_m]> Ξ2) (f2 : Ξ2 =[val_m]> Ξ3) a (t : term Ξ1 a)
: t `ββ (f1 β f2) = (t `ββ f1) `ββ f2 .
now apply (term_ind_mut _ _ _ sub_sub_prf).
Qed.
Lemma v_sub_sub {Ξ1 Ξ2 Ξ3} (f1 : Ξ1 =[val_m]> Ξ2) (f2 : Ξ2 =[val_m]> Ξ3) a (v : val Ξ1 a)
: v `α΅₯β (f1 β f2) = (v `α΅₯β f1) `α΅₯β f2 .
now apply (val_ind_mut _ _ _ sub_sub_prf).
Qed.
Lemma e_sub_sub {Ξ1 Ξ2 Ξ3} (f1 : Ξ1 =[val_m]> Ξ2) (f2 : Ξ2 =[val_m]> Ξ3) a (Ο : ev_ctx Ξ1 a)
: Ο `ββ (f1 β f2) = (Ο `ββ f1) `ββ f2 .
now apply (ctx_ind_mut _ _ _ sub_sub_prf).
Qed.
Lemma m_sub_sub {Ξ1 Ξ2 Ξ3} a (v : val_m Ξ1 a) (f1 : Ξ1 =[val_m]> Ξ2) (f2 : Ξ2 =[val_m]> Ξ3)
: v α΅₯β (f1 β f2) = (v α΅₯β f1) α΅₯β f2 .
destruct a; [ now apply v_sub_sub | now apply e_sub_sub ].
Qed.
Lemma s_sub_sub {Ξ1 Ξ2 Ξ3} (s : state Ξ1) (f1 : Ξ1 =[val_m]> Ξ2) (f2 : Ξ2 =[val_m]> Ξ3)
: s ββ (f1 β f2) = (s ββ f1) ββ f2 .
destruct s; cbn; now rewrite t_sub_sub, e_sub_sub.
Qed.
Lemma a_sub2_sub {Ξ Ξ a b} (f : Ξ =[val_m]> Ξ) (v1 : val_m Ξ a) (v2 : val_m Ξ b)
: a_shift2 f β assign2 (v1 α΅₯β f) (v2 α΅₯β f) β‘β (assign2 v1 v2) β f .
intros ? v.
do 2 (dependent elimination v; [ cbn; now rewrite m_sub_id_r | ]).
cbn; unfold m_shift2; rewrite m_sub_ren, m_sub_id_r.
now apply m_sub_id_l.
Qed.
Lemma t_sub2_sub {Ξ Ξ x y z} (f : Ξ =[val_m]> Ξ) (t : term (Ξ βΆβ x βΆβ y) z) v1 v2
: (t `ββ a_shift2 f) /[ v1 α΅₯β f , v2 α΅₯β f ] = (t /[ v1 , v2 ]) `ββ f.
unfold t_subst2; now rewrite <- 2 t_sub_sub, a_sub2_sub.
Qed.
Having proved all the basic syntactic properties of STLC, we are now ready to instanciate our framework!
As we only have negative types, we instanciate the interaction specification with types and observations. Beware that in more involved cases, the notion of "types" we give to the OGS construction does not coincide with the "language types": you should only give the negative types, or more intuitively, "non-shareable" types.
As hinted at the beginning, we instanciate the abstract value notion with our "machine values". They form a suitable monoid, which means we get a category of assigments, for which we now provide the proof of the laws.
#[global] Instance stlc_val_laws : subst_monoid_laws val_m :=
{| v_sub_proper := @m_sub_eq ;
v_sub_var := @m_sub_id_r ;
v_var_sub := @m_sub_id_l ;
Subst.v_sub_sub := @m_sub_sub |} .
Configurations are instanciated with our states, and what we have proved earlier amounts to showing they are a right-module on values.
#[global] Instance stlc_conf_laws : subst_module_laws val_m state :=
{| c_sub_proper := @s_sub_eq ;
c_var_sub := @s_sub_id_l ;
c_sub_sub := @s_sub_sub |} .
In our generic theorem, there is a finicky lemma that is the counter-part to
the exclusion of any "infinite chit-chat" that one finds in other accounts of
OGS and other game semantics. The way we have proved it requires a little bit
more structure on values. Specifically, we need to show that a_id
is
injective and that its fibers are decidable and invert renamings. These
technicalities are easily shown by induction on values but help us to
distinguish conveniently between values which are variables and others.
#[global] Instance stlc_var_laws : var_assumptions val_m. econstructor. - intros ? [] ?? H; now dependent destruction H. - intros ? [] []; try now apply Yes; exact (Vvar _). all: apply No; intro H; dependent destruction H. - intros ?? [] v r H; induction v; dependent destruction H; exact (Vvar _). Qed.
We now instanciate the machine with stlc_eval
as the active step ("compute
the next observable action") and obs_app
as the passive step ("resume from
a stuck state").
#[global] Instance stlc_machine : machine val_m state obs_op :=
{| eval := @stlc_eval ;
oapp := @obs_app |} .
All that is left is to prove our theorem-specific hypotheses. All but another
technical lemma for the chit-chat problem are again coherence conditions
between eval
and app
and the monoidal structure of values and
configurations.
#[global] Instance stlc_machine_law : machine_laws val_m state obs_op. econstructor; cbn; intros.
The first one proves that obs_app
respects pointwise equality of assigments.
- intros ?? H1; dependent elimination o; cbn; repeat (f_equal; auto).
The second one proves a commutation law of obs_app
with renamings.
- destruct x; dependent elimination o; cbn; f_equal.
The meat of our abstract proof is this next one. We need to prove that our evaluator respects substitution in a suitable sense: evaluating a substituted configuration must be the same thing as evaluating the configuration, then "substituting" the normal form and continuing the evaluation.
While potentially scary, the proof is direct and this actually amount to checking that indeed, when unrolling our evaluator, this is what happens.
- revert c a; unfold comp_eq, it_eq; coinduction R CIH; intros c e. cbn; funelim (eval_step c); cbn. + destruct (e Β¬ a0 i); auto. remember (v `α΅₯β e) as v'; clear H v Heqv'. dependent elimination v'; cbn; auto. + econstructor; refold_eval; apply CIH. + remember (e + (a2 β b0) i) as vv; clear H i Heqvv. dependent elimination vv; cbn; auto. + econstructor; refold_eval; change (Lam (t `ββ _)) with (Lam t `α΅₯β e); change (?v `α΅₯β ?a) with ((v : val_m _ (+ _)) α΅₯β a). rewrite t_sub2_sub. apply CIH. + econstructor; refold_eval; apply CIH.
Just like the above proof had the flavor of a composition law of module, this one has the flavor of an identity law. It states that evaluating a normal form is the identity computation.
- destruct u as [ a i [ p Ξ³ ] ]; cbn. dependent elimination p; cbn. all: unfold comp_eq; apply it_eq_unstep; cbn; econstructor. all: econstructor; intros ? v; do 3 (dependent elimination v; auto).
This last proof is the technical condition we hinted at. It is a proof of well-foundedness of some relation, and what it amounts to is that if we repeatedly instantiate the head variable of a normal form by a value which is not a variable, after a finite number of times doing so we will eventually reach something that is not a normal form.
For our calculus this number is at most 2, the pathological state being
β¨ x | y β©
, which starts by being stuck on y
, but when instanciating by
some non-variable Ο
, β¨ x | Ο β©
is still stuck, this time on x
. After
another step it will definitely be unstuck and evaluation will be able to do
a reduction step.
It is slightly tedious to prove but amount again to a "proof by case splitting".
- intros [ x p ]. destruct x; dependent elimination p; econstructor. * intros [ z p ] H. dependent elimination p; dependent elimination H. all: dependent elimination v; try now destruct (t0 (Vvar _)). all: apply it_eq_step in i0; now inversion i0. * intros [ z p ] H. dependent elimination p; dependent elimination H; cbn in *. dependent elimination v; try now destruct (t0 (Vvar _)). + apply it_eq_step in i0; now inversion i0. + remember (a1 _ Ctx.top) as vv; clear a1 Heqvv. dependent elimination vv; apply it_eq_step in i0; cbn in i0; dependent elimination i0. inversion r_rel. + econstructor; intros [ z p ] H. dependent elimination p; dependent elimination H. all: dependent elimination v0; try now destruct (t1 (Vvar _)). all: apply it_eq_step in i2; now inversion i2. Qed.
At this point we have finished all the hard work! We already enjoy the generic correctness theorem but don't know it yet! Lets define some shorthands for some generic notions applied to our case, to make it a welcoming nest.
The whole semantic is parametrized by typing scope Ξ
of "final channels".
Typically this can be instanciated with the singleton [ Β¬ ans ]
for some
chosen type ans
, which will correspond with the outside type of the
testing-contexts from CIU-equivalence. Usually this answer type is taken among
the positive (or shareable) types of our language, but in fact using our
observation machinery we can project the value of any type onto its "shareable
part". This is why our generic proof abstracts over this answer type and even
allows several of them at the same time (that is, Ξ
). In our case, as all
types in our language are unshareable, the positive part of any value is
pretty useless: it is always a singleton. Yet our notion of testing still
distinguishes terminating from non-terminating programs.
As discussed in the paper, the "native output" of the generic theorem is correctness with respect to an equivalence we call "substitution equivalence". We will recover a more standard CIU later on.
Definition subst_eq Ξ {Ξ} : relation (state Ξ) :=
fun u v => forall Ο : Ξ =[val_m]> Ξ, evalβ (u ββ Ο) β evalβ (v ββ Ο) .
Notation "x βS[ Ξ ]β y" := (subst_eq Ξ x y) (at level 10).
Our semantic objects live in what is defined in the generic construction as
ogs_act
, that is active strategies for the OGS game. They come with their
own notion of equivalence, weak bisimilarity and we get to interpret states
into semantic objects.
Definition sem_act Ξ Ξ := ogs_act (obs := obs_op) Ξ (β
βΆβ Ξ) .
Definition ogs_weq_act Ξ {Ξ} : relation (sem_act Ξ Ξ) := fun u v => u β v .
Notation "u β[ Ξ ]β v" := (ogs_weq_act Ξ u v) (at level 40).
Definition interp_act_s Ξ {Ξ} (c : state Ξ) : sem_act Ξ Ξ :=
m_strat (β
βΆβ Ξ) (inj_init_act Ξ c) .
Notation "OGSβ¦ t β§" := (interp_act_s _ t) .
We can now obtain our instance of the correctness result!
Theorem stlc_subst_correct Ξ {Ξ} (x y : state Ξ)
: OGSβ¦xβ§ β[Ξ]β OGSβ¦yβ§ -> x βS[Ξ]β y .
exact (ogs_correction Ξ x y).
Qed.
CIU-equivalence more usually defined as a relation on terms (and not some states), and involves an evaluation context. In our formalism it amounts to the following definition.
Definition ciu_eq Ξ {Ξ a} : relation (term Ξ a) :=
fun u v => forall (Ο : Ξ =[val_m]> Ξ) (Ο : ev_ctx Ξ a),
evalβ ((u `ββ Ο) β
Ο) β evalβ ((v `ββ Ο) β
Ο) .
Notation "x βC[ Ξ ]β y" := (ciu_eq Ξ x y) (at level 10).
Now from a term we can always construct a state by naming it, that is, placing the term opposite of a fresh context variable.
Definition c_init {Ξ a} (t : term Ξ a) : state (Ξ βΆβ Β¬ a)
:= t_shift _ t β
K0 Ctx.top .
Notation "Tβ¦ t β§" := (OGSβ¦ c_init t β§) .
Similarly, from an evaluation context and a substitution, we can form an extended substitution. Without surprise these two constructions simplify well in terms of substitution.
Definition a_of_sk {Ξ Ξ a} (Ο : Ξ =[val_m]> Ξ) (Ο : ev_ctx Ξ a)
: (Ξ βΆβ Β¬ a) =[val_m]> Ξ := Ο βΆβ (Ο : val_m _ (Β¬ _)) .
Lemma sub_init {Ξ Ξ a} (t : term Ξ a) (Ο : Ξ =[val_m]> Ξ) (Ο : ev_ctx Ξ a)
: (t `ββ Ο) β
Ο = c_init t ββ a_of_sk Ο Ο .
cbn; unfold t_shift; now rewrite t_sub_ren.
Qed.
We can now obtain a correctness theorem with respect to standard CIU-equivalence by embedding terms into states. Proving that CIU-equivalence entails our substitution equivalence is left to the reader!
Theorem stlc_ciu_correct Ξ {Ξ a} (x y : term Ξ a)
: Tβ¦ x β§ β[Ξ]β Tβ¦ y β§ -> x βC[Ξ]β y .
intros H Ο k; rewrite 2 sub_init.
now apply stlc_subst_correct.
Qed.
[AACMM21] | Guillaume Allais et al, "A type- and scope-safe universe of syntaxes with binding: their semantics and proofs", 2021. |
[FS22] | Marcelo Fiore & Dmitrij Szamozvancev, "Formal Metatheory of Second-Order Abstract Syntax", 2022. |
[L05] | Soren Lassen, "Eager Normal Form Bisimulation", 2005. |