Built with Alectryon, running Coq+SerAPI v8.17.0+0.17.3. Bubbles () indicate interactive fragments: hover for details, tap to reveal contents. Use Ctrl+↑ Ctrl+↓ to navigate, Ctrl+πŸ–±οΈ to focus. On Mac, use ⌘ instead of Ctrl.

Un(i)typed Call-by-value Lambda Calculus

This file follows very closely its sibling CBVTyped.v. It is recommended to read that one first: the comments on this one will be quite more terse and focus on the differences.

Syntax

As our framework is "well-typed well-scoped", when we mean untyped, we really mean unityped, i.e., we will take the single element set as set of types. Although, the typing rules will not be as uninteresting as it would seem. Indeed, as we adopt a single-sided sequent-calculus style of presentation, we do give types to continuations (formally negated types), and we thus have not one type, but two: the type of terms βŠ• and the type of continuations βŠ–.

Typing contexts, terms, values, evaluation contexts and configurations work straightforwardly.

Variant ty : Type :=
| Left : ty
| Right : ty
.
Notation "βŠ•" := (Left) (at level 20) : ty_scope .
Notation "βŠ–" := (Right) (at level 20) : ty_scope .

The reference Ctx.ctx was not found in the current environment.
Bind Scope ctx_scope with t_ctx .

Note that is is the proper pure untyped lambda calculus: in constrast with our STLC example, the lambda is not recursive and there is no unit value nor base type, only functions.

Inductive term (Ξ“ : t_ctx) : Type :=
| Val : val Ξ“ -> term Ξ“
| App : term Ξ“ -> term Ξ“ -> term Ξ“
with val (Ξ“ : t_ctx) : Type :=
| Var : Ξ“ βˆ‹ βŠ• -> val Ξ“
| Lam : term (Ξ“ β–Άβ‚“ βŠ•) -> val Ξ“
.
Inductive ev_ctx (Ξ“ : t_ctx) : Type :=
| K0 : Ξ“ βˆ‹ βŠ– -> ev_ctx Ξ“
| K1 : term Ξ“ -> ev_ctx Ξ“ -> ev_ctx Ξ“
| K2 : val Ξ“ -> ev_ctx Ξ“ -> ev_ctx Ξ“
.
Variant state (Ξ“ : t_ctx) : Type :=
| Cut : term Ξ“ -> ev_ctx Ξ“ -> state Ξ“
.

We introduce the sorted family of generalized values, together with the generalized variable.

Equations val_m : t_ctx -> ty -> Type :=
  val_m Ξ“ (βŠ•) := val Ξ“ ;
  val_m Ξ“ (βŠ–) := ev_ctx Ξ“ .

Equations a_id {Ξ“} : Ξ“ =[val_m]> Ξ“ :=
  a_id (βŠ•) i := Var i ;
  a_id (βŠ–) i := K0 i .

Substitution and Renaming

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 (Lam t) := Lam (t_rename (r_shift1 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 Ο€ .

Renaming an assigment on the left.

Definition a_ren {Ξ“1 Ξ“2 Ξ“3} : Ξ“1 =[val_m]> Ξ“2 -> Ξ“2 βŠ† Ξ“3 -> Ξ“1 =[val_m]> Ξ“3 :=
  fun f g _ i => m_rename g _ (f _ i) .
Arguments a_ren _ _ _ /.

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 a%asgn r%asgn) (at level 14) : asgn_scope.

The weakenings we will need for substitution..

Definition t_shift1 {Ξ“ a} := @t_rename Ξ“ (Ξ“ β–Άβ‚“ a) r_pop.
Definition v_shift1 {Ξ“ a} := @v_rename Ξ“ (Ξ“ β–Άβ‚“ a) r_pop.
Definition m_shift1 {Ξ“ a} := @m_rename Ξ“ (Ξ“ β–Άβ‚“ a) r_pop.
Definition a_shift1 {Ξ“ Ξ” a} (f : Ξ“ =[val_m]> Ξ”) :=
  [ a_map f m_shift1 ,β‚“ a_id a top ]%asgn .

Substitutions

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 (Lam t) := Lam (t_subst (a_shift1 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 Ο€) .

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).

Equations m_subst : val_m ⇒₁ ⟦ val_m , val_m βŸ§β‚ :=
  m_subst _ (βŠ•) v _ f := v `α΅₯βŠ› f ;
  m_subst _ (βŠ–) Ο€ _ f := Ο€ `β‚‘βŠ› f .

Equations s_subst : state β‡’β‚€ ⟦ val_m , state βŸ§β‚€ :=
  s_subst _ (Cut t Ο€) _ f := Cut (t `β‚œβŠ› f) (Ο€ `β‚‘βŠ› f) .

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 |} .

Single-variable substitution.

Definition assign1 {Ξ“ a} v : (Ξ“ β–Άβ‚“ a) =[val_m]> Ξ“ := a_id ▢ₐ v .
Definition t_subst1 {Ξ“ a} (t : term _) v := t `β‚œβŠ› @assign1 Ξ“ a v.
Notation "t /[ v ]" := (t_subst1 t v) (at level 50, left associativity).

An Evaluator

Patterns and Observations

As before we define observations (copatterns), as there are only functions and continuations there is exactly one pattern at each type. Knowing this, we could have made this type (obs) disappear, but it is kept here for the sake of being more explicit.

Variant obs : ty -> Type :=
| ORet : obs (βŠ–)
| OApp : obs (βŠ•)
.

Observation still behave as binders, returning bind a term (what we are returning) and applying binds a term (the argument) and a continuation.

Equations obs_dom {a} : obs a -> t_ctx :=
  obs_dom (@ORet) := βˆ… β–Άβ‚“ βŠ• ;
  obs_dom (@OApp) := βˆ… β–Άβ‚“ βŠ• β–Άβ‚“ βŠ– .

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 _).

We now define applying an observation with arguments to a value.

Equations obs_app {Ξ“ a} (v : val_m Ξ“ a) (p : obs a) (Ξ³ : obs_dom p =[val_m]> Ξ“) : state Ξ“ :=
  obs_app v (OApp) Ξ³ := Cut (Val v) (K2 (Ξ³ _ (pop top)) (Ξ³ _ top)) ;
  obs_app Ο€ (ORet) Ξ³ := Cut (Val (Ξ³ _ top)) Ο€ .

Normal forms

Normal forms take the exact same form as for ULC: a head variable and an observation on it, with arguments.

Definition nf := c_var βˆ₯β‚› (obs_op # val_m).

The CBV Machine

The evaluator as a state machine.

  1. ⟨ t1 t2 | Ο€ ⟩ β†’ ⟨ t2 | t1 β‹…1 Ο€ ⟩
  2. ⟨ v | x ⟩ normal
  3. ⟨ v | t β‹…1 Ο€ ⟩ β†’ ⟨ t | v β‹…2 Ο€ ⟩
  4. ⟨ x | v β‹…2 Ο€ ⟩ normal
  5. ⟨ Ξ»x.t | v β‹…2 Ο€ ⟩ β†’ ⟨ 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 Ξ“ -> (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 /[ v ]) Ο€) .

Definition ulc_eval {Ξ“ : t_ctx} : state Ξ“ -> delay (nf Ξ“)
  := iter_delay (ret_delay ∘ eval_step).

Properties

We now tackle the basic syntactic lemmas on renaming and substitution. See you in 400 lines.

Scheme term_mut := Induction for term Sort Prop
   with val_mut := Induction for val Sort Prop .

Record syn_ind_args (P_t : forall Ξ“, term Ξ“ -> Prop)
                    (P_v : forall Ξ“, val Ξ“ -> Prop)
                    (P_e : forall Ξ“, ev_ctx Ξ“ -> Prop) :=
  {
    ind_val {Ξ“} v (_ : P_v Ξ“ v) : P_t Ξ“ (Val v) ;
    ind_app {Ξ“} t1 (_ : P_t Ξ“ t1) t2 (_ : P_t Ξ“ t2) : P_t Ξ“ (App t1 t2) ;
    ind_var {Ξ“} i : P_v Ξ“ (Var i) ;
    ind_lam {Ξ“} t (_ : P_t _ t) : P_v Ξ“ (Lam t) ;
    ind_kvar {Ξ“} i : P_e Ξ“ (K0 i) ;
    ind_kfun {Ξ“} t (_ : P_t Ξ“ t) Ο€ (_ : P_e Ξ“ Ο€) : P_e Ξ“ (K1 t Ο€) ;
    ind_karg {Ξ“} v (_ : P_v Ξ“ v) Ο€ (_ : P_e Ξ“ Ο€) : P_e Ξ“ (K2 v Ο€)
  } .

Lemma term_ind_mut P_t P_v P_e (H : syn_ind_args P_t P_v P_e) Ξ“ t : P_t Ξ“ 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) Ξ“ v : P_v Ξ“ 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) Ξ“ Ο€ : P_e Ξ“ Ο€ .
  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.

Renaming respects pointwise equality of assignments.

Definition t_ren_proper_P Ξ“ (t : term Ξ“) : Prop :=
  forall Ξ” (f1 f2 : Ξ“ βŠ† Ξ”), f1 ≑ₐ f2 -> t β‚œβŠ›α΅£ f1 = t β‚œβŠ›α΅£ f2 .
Definition v_ren_proper_P Ξ“ (v : val Ξ“) : Prop :=
  forall Ξ” (f1 f2 : Ξ“ βŠ† Ξ”), f1 ≑ₐ f2 -> v α΅₯βŠ›α΅£ f1 = v α΅₯βŠ›α΅£ f2 .
Definition e_ren_proper_P Ξ“ (Ο€ : ev_ctx Ξ“) : 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; auto.
  all: apply H.
  now apply r_shift1_eq.
Qed.

#[global] Instance t_ren_eq {Ξ“ Ξ”}
  : Proper (asgn_eq _ _ ==> eq ==> eq) (@t_rename Ξ“ Ξ”).
  intros f1 f2 H1 x y ->; now apply (term_ind_mut _ _ _ ren_proper_prf).
Qed.
#[global] Instance v_ren_eq {Ξ“ Ξ”}
  : Proper (asgn_eq _ _ ==> eq ==> eq) (@v_rename Ξ“ Ξ”).
  intros f1 f2 H1 x y ->; now apply (val_ind_mut _ _ _ ren_proper_prf).
Qed.
#[global] Instance e_ren_eq {Ξ“ Ξ”}
  : Proper (asgn_eq _ _ ==> eq ==> eq) (@e_rename Ξ“ Ξ”).
  intros f1 f2 H1 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; 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 _ _ H2), H1.
Qed.
#[global] Instance a_shift_eq {Ξ“ Ξ” x} : Proper (asgn_eq _ _ ==> asgn_eq _ _) (@a_shift1 Ξ“ Ξ” x).
  intros ? ? H ? h; dependent elimination h; cbn; auto.
  now rewrite H.
Qed.

Renaming-renaming assocativity.

Definition t_ren_ren_P Ξ“1 (t : term Ξ“1) : Prop :=
  forall Ξ“2 Ξ“3 (f1 : Ξ“1 βŠ† Ξ“2) (f2 : Ξ“2 βŠ† Ξ“3),
    (t β‚œβŠ›α΅£ f1) β‚œβŠ›α΅£ f2 = t β‚œβŠ›α΅£ (f1 α΅£βŠ› f2)  .
Definition v_ren_ren_P Ξ“1 (v : val Ξ“1) : Prop :=
  forall Ξ“2 Ξ“3 (f1 : Ξ“1 βŠ† Ξ“2) (f2 : Ξ“2 βŠ† Ξ“3),
    (v α΅₯βŠ›α΅£ f1) α΅₯βŠ›α΅£ f2 = v α΅₯βŠ›α΅£ (f1 α΅£βŠ› f2)  .
Definition e_ren_ren_P Ξ“1 (Ο€ : ev_ctx Ξ“1) : 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 ? h; dependent elimination h; auto.
Qed.

Lemma t_ren_ren {Ξ“1 Ξ“2 Ξ“3} (f1 : Ξ“1 βŠ† Ξ“2) (f2 : Ξ“2 βŠ† Ξ“3) (t : term Ξ“1)
  : (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) (v : val Ξ“1)
  : (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) (Ο€ : ev_ctx Ξ“1)
  : (Ο€ β‚‘βŠ›α΅£ 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.

Left identity law of renaming.

Definition t_ren_id_l_P Ξ“ (t : term Ξ“) : Prop := t β‚œβŠ›α΅£ r_id = t .
Definition v_ren_id_l_P Ξ“ (v : val Ξ“) : Prop := v α΅₯βŠ›α΅£ r_id = v .
Definition e_ren_id_l_P Ξ“ (Ο€ : ev_ctx Ξ“) : 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 ? h; dependent elimination h; auto.
Qed.

Lemma t_ren_id_l {Ξ“} (t : term Ξ“) : t β‚œβŠ›α΅£ r_id = t.
  now apply (term_ind_mut _ _ _ ren_id_l_prf).
Qed.
Lemma v_ren_id_l {Ξ“} (v : val Ξ“) : v α΅₯βŠ›α΅£ r_id = v .
  now apply (val_ind_mut _ _ _ ren_id_l_prf).
Qed.
Lemma e_ren_id_l {Ξ“} (Ο€ : ev_ctx Ξ“) : Ο€ β‚‘βŠ›α΅£ 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 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_shift_id {Ξ“ x} : @a_shift1 Ξ“ Ξ“ x a_id ≑ₐ a_id.
  intros ? v; dependent elimination v; auto.
  exact (m_ren_id_r _ _).
Qed.

Lemma 5: shifting assigments commutes with left and right renaming.

Lemma a_shift1_s_ren {Ξ“1 Ξ“2 Ξ“3 a} (f1 : Ξ“1 βŠ† Ξ“2) (f2 : Ξ“2 =[val_m]> Ξ“3)
  : @a_shift1 _ _ a (f1 α΅£βŠ› f2) ≑ₐ r_shift1 f1 α΅£βŠ› a_shift1 f2 .
  intros ? v; dependent elimination v; auto.
Qed.
Lemma a_shift1_a_ren {Ξ“1 Ξ“2 Ξ“3 a} (f1 : Ξ“1 =[val_m]> Ξ“2) (f2 : Ξ“2 βŠ† Ξ“3)
      : @a_shift1 _ _ a (f1 βŠ›α΅£ f2) ≑ₐ a_shift1 f1 βŠ›α΅£ r_shift1 f2 .
  intros ? v.
  dependent elimination v.
  cbn; now rewrite m_ren_id_r.
  cbn; unfold m_shift1, r_shift1; now rewrite 2 m_ren_ren.
Qed.

Lemma 6: substitution respects pointwise equality of assigments.

Definition t_sub_proper_P Ξ“ (t : term Ξ“) : Prop :=
  forall Ξ” (f1 f2 : Ξ“ =[val_m]> Ξ”), f1 ≑ₐ f2 -> t `β‚œβŠ› f1 = t `β‚œβŠ› f2 .
Definition v_sub_proper_P Ξ“ (v : val Ξ“) : Prop :=
  forall Ξ” (f1 f2 : Ξ“ =[val_m]> Ξ”), f1 ≑ₐ f2 -> v `α΅₯βŠ› f1 = v `α΅₯βŠ› f2 .
Definition e_sub_proper_P Ξ“ (Ο€ : ev_ctx Ξ“) : 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_shift_eq.
Qed.

#[global] Instance t_sub_eq {Ξ“ Ξ”}
  : Proper (asgn_eq _ _ ==> eq ==> eq) (@t_subst Ξ“ Ξ”).
  intros ? ? H1 _ ? ->; now apply (term_ind_mut _ _ _ sub_proper_prf).
Qed.
#[global] Instance v_sub_eq {Ξ“ Ξ”}
  : Proper (asgn_eq _ _ ==> eq ==> eq) (@v_subst Ξ“ Ξ”).
  intros ? ? H1 _ ? ->; now apply (val_ind_mut _ _ _ sub_proper_prf).
Qed.
#[global] Instance e_sub_eq {Ξ“ Ξ”}
  : Proper (asgn_eq _ _ ==> eq ==> eq) (@e_subst Ξ“ Ξ”).
  intros ? ? H1 _ ? ->; 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 (t : term Ξ“1) : Prop :=
  forall Ξ“2 Ξ“3 (f1 : Ξ“1 =[val_m]> Ξ“2) (f2 : Ξ“2 βŠ† Ξ“3) ,
    (t `β‚œβŠ› f1) β‚œβŠ›α΅£ f2 = t `β‚œβŠ› (f1 βŠ›α΅£ f2) .
Definition v_ren_sub_P Ξ“1 (v : val Ξ“1) : Prop :=
  forall Ξ“2 Ξ“3 (f1 : Ξ“1 =[val_m]> Ξ“2) (f2 : Ξ“2 βŠ† Ξ“3) ,
    (v `α΅₯βŠ› f1) α΅₯βŠ›α΅£ f2 = v `α΅₯βŠ› (f1 βŠ›α΅£ f2) .
Definition e_ren_sub_P Ξ“1 (Ο€ : ev_ctx Ξ“1) : 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; auto.
  change (a_shift1 (fun x => _)) with (a_shift1 (a:=βŠ•) (f1 βŠ›α΅£ f2)); now rewrite a_shift1_a_ren.
Qed.

Lemma t_ren_sub {Ξ“1 Ξ“2 Ξ“3} (f1 : Ξ“1 =[val_m]> Ξ“2) (f2 : Ξ“2 βŠ† Ξ“3) (t : term Ξ“1)
  : (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) (v : val Ξ“1)
  : (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) (Ο€ : ev_ctx Ξ“1)
  : (Ο€ `β‚‘βŠ› 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 (t : term Ξ“1) : Prop :=
  forall Ξ“2 Ξ“3 (f1 : Ξ“1 βŠ† Ξ“2) (f2 : Ξ“2 =[val_m]> Ξ“3) ,
  (t β‚œβŠ›α΅£ f1) `β‚œβŠ› f2 = t `β‚œβŠ› (f1 α΅£βŠ› f2) .
Definition v_sub_ren_P Ξ“1 (v : val Ξ“1) : Prop :=
  forall Ξ“2 Ξ“3 (f1 : Ξ“1 βŠ† Ξ“2) (f2 : Ξ“2 =[val_m]> Ξ“3) ,
  (v α΅₯βŠ›α΅£ f1) `α΅₯βŠ› f2 = v `α΅₯βŠ› (f1 α΅£βŠ› f2) .
Definition e_sub_ren_P Ξ“1 (Ο€ : ev_ctx Ξ“1) : 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_shift1_s_ren.
Qed.

Lemma t_sub_ren {Ξ“1 Ξ“2 Ξ“3} (f1 : Ξ“1 βŠ† Ξ“2) (f2 : Ξ“2 =[val_m]> Ξ“3) (t : term Ξ“1)
  : (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) (v : val Ξ“1)
  : (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) (Ο€ : ev_ctx Ξ“1)
  : (Ο€ β‚‘βŠ›α΅£ 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 Ξ“ (t : term Ξ“) : Prop := t `β‚œβŠ› a_id = t .
Definition v_sub_id_l_P Ξ“ (v : val Ξ“) : Prop := v `α΅₯βŠ› a_id = v .
Definition e_sub_id_l_P Ξ“ (Ο€ : ev_ctx Ξ“) : 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_shift_id.
Qed.

Lemma t_sub_id_l {Ξ“} (t : term Ξ“) : t `β‚œβŠ› a_id = t .
  now apply (term_ind_mut _ _ _ sub_id_l_prf).
Qed.
Lemma v_sub_id_l {Ξ“} (v : val Ξ“) : v `α΅₯βŠ› a_id = v .
  now apply (val_ind_mut _ _ _ sub_id_l_prf).
Qed.
Lemma e_sub_id_l {Ξ“} (Ο€ : ev_ctx Ξ“) : Ο€ `β‚‘βŠ› 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_shift1_comp {Ξ“1 Ξ“2 Ξ“3 a} (f1 : Ξ“1 =[val_m]> Ξ“2) (f2 : Ξ“2 =[val_m]> Ξ“3) 
  : @a_shift1 _ _ a (f1 βŠ› f2) ≑ₐ a_shift1 f1 βŠ› a_shift1 f2 .
  intros ? v; dependent elimination v; cbn.
  now rewrite m_sub_id_r.
  unfold m_shift1; now rewrite m_ren_sub, m_sub_ren.
Qed.

Lemma 11: substitution-substitution associativity, ie composition law.

Definition t_sub_sub_P Ξ“1 (t : term Ξ“1) : 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 (v : val Ξ“1) : 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 (Ο€ : ev_ctx Ξ“1) : 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_shift1_comp.
Qed.

Lemma t_sub_sub {Ξ“1 Ξ“2 Ξ“3} (f1 : Ξ“1 =[val_m]> Ξ“2) (f2 : Ξ“2 =[val_m]> Ξ“3) (t : term Ξ“1)
  : 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) (v : val Ξ“1)
  : 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) (Ο€ : ev_ctx Ξ“1)
  : Ο€ `β‚‘βŠ› (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_sub1_sub {Ξ“ Ξ” a} (f : Ξ“ =[val_m]> Ξ”) (v : val_m Ξ“ a)
  : a_shift1 f βŠ› assign1 (v α΅₯βŠ› f) ≑ₐ (assign1 v) βŠ› f .
  intros ? i; dependent elimination i; cbn.
  now rewrite m_sub_id_r.
  unfold m_shift1; rewrite m_sub_ren, m_sub_id_r; now apply m_sub_id_l.
Qed.
Lemma t_sub1_sub {Ξ“ Ξ” x} (f : Ξ“ =[val_m]> Ξ”) (t : term (Ξ“ β–Άβ‚“ x)) v
  : (t `β‚œβŠ› a_shift1 f) /[ v α΅₯βŠ› f ] = (t /[ v ]) `β‚œβŠ› f.
  unfold t_subst1; now rewrite <- 2 t_sub_sub, a_sub1_sub.
Qed.

The Actual Instance

We can now instanciate the generic OGS construction.

#[global] Instance ulc_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 |} .

#[global] Instance ulc_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 |} .

#[global] Instance ulc_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.

#[global] Instance ulc_machine : machine val_m state obs_op :=
  {| eval := @ulc_eval ;
     oapp := @obs_app |} .

From Coinduction Require Import coinduction lattice rel tactics.
From OGS.ITree Require Import Eq.

Ltac refold_eval :=
  change (Structure.iter _ _ ?a) with (ulc_eval a);
  change (Structure.subst (fun pat : T1 => let 'T1_0 := pat in ?f) T1_0 ?u)
    with (bind_delay' u f).

#[global] Instance ulc_machine_law : machine_laws val_m state obs_op.
  econstructor; cbn; intros.
  - intros ?? H1; dependent elimination o; cbn; repeat (f_equal; auto).
  - destruct x; dependent elimination o; cbn; f_equal.
  - revert c a; unfold comp_eq, it_eq; coinduction R CIH; intros c e.
    cbn; funelim (eval_step c); cbn.
    + destruct (e (βŠ–) i); auto.
      remember (v `α΅₯βŠ› e) as v'; clear H v Heqv'.
      dependent elimination v'; cbn; auto.
    + econstructor; refold_eval; apply CIH.
    + remember (e (βŠ•) i) as vv; clear H i Heqvv.
      dependent elimination vv; cbn; auto.
    + econstructor;
       refold_eval;
       change (?v `α΅₯βŠ› ?a) with ((v : val_m _ (βŠ•)) α΅₯βŠ› a).
      rewrite t_sub1_sub.
      apply CIH.
    + econstructor; refold_eval; apply CIH.
  - 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).
  - 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 (a _ Ctx.top) as vv; clear a 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.

Concrete soundness theorem

Definition subst_eq Ξ” {Ξ“} : relation (state Ξ“) :=
  fun u v => forall Οƒ : Ξ“ =[val_m]> Ξ”, evalβ‚’ (u β‚œβŠ› Οƒ) β‰ˆ evalβ‚’ (v β‚œβŠ› Οƒ) .
Notation "x β‰ˆβŸ¦sub Ξ” βŸ§β‰ˆ y" := (subst_eq Ξ” x y) (at level 10).

Theorem ulc_subst_correct Ξ” {Ξ“} (x y : state Ξ“)
  : x β‰ˆβŸ¦ogs Ξ”βŸ§β‰ˆ y -> x β‰ˆβŸ¦sub Ξ”βŸ§β‰ˆ y .
  exact (ogs_correction Ξ” x y).
Qed.

Recovering CIU-equivalence

Definition ciu_eq Ξ” {Ξ“} : relation (term Ξ“) :=
  fun u v => forall (Οƒ : Ξ“ =[val_m]> Ξ”) (Ο€ : ev_ctx Ξ”),
      evalβ‚’ (Cut (u `β‚œβŠ› Οƒ) Ο€) β‰ˆ evalβ‚’ (Cut (v `β‚œβŠ› Οƒ) Ο€) .
Notation "x β‰ˆβŸ¦ciu Ξ” βŸ§β‰ˆ y" := (ciu_eq Ξ” x y) (at level 10).

Definition c_init {Ξ“} (t : term Ξ“) : state (Ξ“ β–Άβ‚“ βŠ–)
  := Cut (t_shift1 t) (K0 Ctx.top) .

Definition a_of_sk {Ξ“ Ξ”} (Οƒ : Ξ“ =[val_m]> Ξ”) (Ο€ : ev_ctx Ξ”)
  : (Ξ“ β–Άβ‚“ βŠ–) =[val_m]> Ξ” := Οƒ ▢ₐ (Ο€ : val_m _ (βŠ–)) .

Lemma sub_init {Ξ“ Ξ”} (t : term Ξ“) (Οƒ : Ξ“ =[val_m]> Ξ”) (Ο€ : ev_ctx Ξ”)
  : Cut (t `β‚œβŠ› Οƒ) Ο€ = c_init t β‚œβŠ› a_of_sk Οƒ Ο€  .
  cbn; unfold t_shift1; 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 ulc_ciu_correct Ξ” {Ξ“} (x y : term Ξ“)
  : c_init x β‰ˆβŸ¦ogs Ξ”βŸ§β‰ˆ c_init y -> x β‰ˆβŸ¦ciu Ξ”βŸ§β‰ˆ y .
  intros H Οƒ k; rewrite 2 sub_init.
  now apply ulc_subst_correct.
Qed.

Example terms

Following a trick by Conor McBride we provide a cool notation for writing terms without any DeBruijn indices, instead leveraging Coq's binders. For this we need a bit of infrastructure, for manipulating terms that only have term variables.

Equations n_ctx : nat -> t_ctx :=
  n_ctx O     := βˆ… ;
  n_ctx (S n) := n_ctx n β–Άβ‚“ βŠ• .

Notation var' n := (n_ctx n βˆ‹ βŠ•).
Notation term' n := (term (n_ctx n)).

Equations v_emb (n : nat) : var' (S n) :=
  v_emb O     := Ctx.top ;
  v_emb (S n) := pop (v_emb n) .

Equations v_lift {n} : var' n -> var' (S n) :=
  @v_lift (S _) Ctx.top     := Ctx.top ;
  @v_lift (S _) (pop i) := pop (v_lift i) .

Here starts the magic.

Equations mk_var (m : nat) : forall n, var' (m + S n) :=
  mk_var O     := v_emb ;
  mk_var (S m) := v_lift ∘ mk_var m .

Definition mk_lam {m : nat} (f : (forall n, term' (m + S n)) -> term' (S m)) : term' m
  := Val (Lam (f (Val ∘ Var ∘ mk_var m))).
Arguments mk_lam {_} & _ .

Now a bit of syntactic sugar.

Declare Custom Entry lambda.
Notation "✨ e ✨" := (e : term' 0) (e custom lambda at level 2).
Notation "x" := (x _) (in custom lambda at level 0, x ident).
Notation "( x )" := x (in custom lambda at level 0, x at level 2).
Notation "x y" := (App x y)
  (in custom lambda at level 1, left associativity).
Notation "'Ξ»' x .. y β‡’ U" :=
  (mk_lam (fun x => .. (mk_lam (fun y => U)) ..))
  (in custom lambda at level 1, x binder, y binder, U at level 2).

Aren't these beautiful?

Definition Delta := ✨ (Ξ» x β‡’ x x) ✨ .
Definition Omega := ✨ (Ξ» x β‡’ x x) (Ξ» x β‡’ x x) ✨ .
Definition Upsilon := ✨ Ξ» f β‡’ (Ξ» x β‡’ f (x x)) (Ξ» x β‡’ f (x x)) ✨.
Definition Theta := ✨ (Ξ» x f β‡’ f (x x f)) (Ξ» x f β‡’ f (x x f)) ✨.

You can check the actual lambda-term they unwrap to by running eg:

Eval cbv in Upsilon.