From actris.channel Require Import proofmode proto channel.
From actris.logrel Require Import session_types subtyping_rules
     term_typing_judgment term_typing_rules session_typing_rules
     environments telescopes napp.
From actris.utils Require Import llist.
From actris.logrel.lib Require Import par_start.
From iris.proofmode Require Import tactics.

Definition mapper_service_aux : expr := λ: "f" "c",
  (rec: "go" "f" "c":=
    (branch [1%Z;2%Z]) "c"
    (λ: "c", let: "v" := recv "c" in
     send "c" ("f" "v");; "go" "f" "c")
    (λ: "c", #())) "f" "c".

Definition mapper_service : expr :=
  λ: "c",
    let: "f" := recv "c" in
    mapper_service_aux "f" "c".

Definition send_all : val :=
  rec: "go" "c" "xs" :=
    if: lisnil "xs" then #() else
      send "c" #1;; send "c" (lpop "xs");; "go" "c" "xs".

Definition recv_all : val :=
  rec: "go" "c" "ys" "n" :=
    if: "n" = #0 then #() else
      let: "x" := recv "c" in
      "go" "c" "ys" ("n"-#1);; lcons "x" "ys".

Definition mapper_client : expr :=
  (λ: "f" "xs" "c",
   send "c" "f";;
   let: "n" := llength "xs" in
   send_all "c" "xs";; recv_all "c" "xs" "n";; send "c" #2%Z;; "xs")%E.

Definition mapper_prog : expr :=
  (λ: "f" "xs",
   par_start (mapper_service) (mapper_client "f" "xs"))%E.

Section mapper_example.
  Context `{heapG Σ, chanG Σ}.

  Definition mapper_type_rec_service_aux (A : ltty Σ) (B : ltty Σ) (rec : lsty Σ)
    : lsty Σ :=
    lty_branch
      (<[1%Z := (<??> TY A; <!!> TY B ; rec)%lty]>
       (<[2%Z := END%lty]>∅)).
  Instance mapper_type_rec_service_contractive A B :
    Contractive (mapper_type_rec_service_aux A B).
  Proof. solve_proto_contractive. Qed.
  Definition mapper_type_rec_service A B : lsty Σ :=
    lty_rec (mapper_type_rec_service_aux A B).

  Lemma ltyped_mapper_aux_service Γ A B :
    ⊢ Γ ⊨ mapper_service_aux : (A → B) ⊸ lty_chan (mapper_type_rec_service A B) ⊸
                                       () ⫤ Γ.
  Proof.
    iApply (ltyped_lam []).
    iApply (ltyped_lam [EnvItem "f" _]).
    iApply ltyped_app; [ by iApply ltyped_var | ].
    iApply ltyped_app; [ by iApply ltyped_var | ].
    iApply (ltyped_subsumption _ _ _ _ _ _
              ((A → B) → lty_chan (mapper_type_rec_service A B) ⊸ ())%lty);
      [ | iApply lty_le_copy_elim | iApply env_le_refl | ].
    { iApply env_le_cons. iApply lty_le_copy_inv_elim. iApply env_le_refl. }
    iApply ltyped_post_nil.
    iApply (ltyped_rec []).
    iApply (ltyped_lam [EnvItem "go" _; EnvItem "f" _]).
    iApply ltyped_post_nil.
    iApply ltyped_app.
    { iApply (ltyped_lam []). iApply ltyped_post_nil. iApply ltyped_unit. }
    iApply ltyped_app.
    {
      simpl. rewrite !(Permutation_swap (EnvItem "f" _))
                     !(Permutation_swap (EnvItem "go" _)).
      iApply (ltyped_lam [EnvItem "go" _; EnvItem "f" _]).
      iApply ltyped_post_nil.
      iApply ltyped_let; [ by iApply ltyped_recv | ].
      iApply ltyped_seq.
      { iApply (ltyped_send _
                  [EnvItem "f" _; EnvItem "v" _; EnvItem "c" _; EnvItem "go" _]);
          [ done | ].
        iApply ltyped_app_copy; [ by iApply ltyped_var | ]=> /=.
        rewrite !(Permutation_swap (EnvItem "f" _)).
        by iApply ltyped_var. }
      simpl. rewrite !(Permutation_swap (EnvItem "f" _)).
      iApply ltyped_subsumption; [ | iApply lty_le_refl | iApply env_le_refl | ].
      { iApply env_le_cons; [ | iApply env_le_refl ].
        iApply lty_le_copy_inv_elim_copyable. iApply lty_copyable_arr_copy. }
      iApply ltyped_app; [ by iApply ltyped_var | ].
      iApply ltyped_app; [ by iApply ltyped_var | ].
      simpl. rewrite !(Permutation_swap (EnvItem "go" _)).
      iApply ltyped_subsumption; [ iApply env_le_refl | | iApply env_le_refl | ].
      { iApply lty_le_copy_elim. }
      by iApply ltyped_var. }
    iApply ltyped_app; [ by iApply ltyped_var | ].
    iApply ltyped_subsumption; [ iApply env_le_refl | | iApply env_le_refl | ].
    { iApply lty_le_arr; [ | iApply lty_le_refl ]. iApply lty_le_chan.
      iApply lty_le_l; [ iApply lty_le_rec_unfold | iApply lty_le_refl ]. }
    iApply ltyped_branch. intros x. rewrite -elem_of_dom. set_solver.
  Qed.

  Definition mapper_type_service : lsty Σ :=
    <?? A B> TY A → B ; mapper_type_rec_service A B.

  Lemma ltyped_mapper_service Γ :
    ⊢ Γ ⊨ mapper_service : lty_chan (mapper_type_service) ⊸ () ⫤ Γ.
  Proof.
    iApply (ltyped_lam []).
    iApply ltyped_post_nil.
    iApply ltyped_recv_texist; [ done | apply _ | ].
    iIntros (Ys).
    iApply ltyped_app; [ by iApply ltyped_var | ].
    iApply ltyped_app; [ by iApply ltyped_var | ].
    pose proof (ltys_S_inv Ys) as [A [Ks HYs]].
    pose proof (ltys_S_inv Ks) as [B [Ks' HKs]].
    pose proof (ltys_O_inv Ks') as HKs'.
    rewrite HYs HKs HKs' /=.
    iApply (ltyped_subsumption _ []);
      [ iApply env_le_nil | iApply lty_le_refl | iApply env_le_refl | ].
    iApply ltyped_mapper_aux_service.
  Qed.

  Definition mapper_type_rec_client_aux
             (A : ltty Σ) (B : ltty Σ) (rec : lsty Σ) : lsty Σ :=
    lty_select (<[1%Z := (<!!> TY A; <??> TY B ; rec)%lty]>
                (<[2%Z := END%lty]>∅)).
  Instance mapper_type_rec_client_contractive A B :
    Contractive (mapper_type_rec_client_aux A B).
  Proof. solve_proto_contractive. Qed.
  Definition mapper_type_rec_client A B : lsty Σ :=
    lty_rec (mapper_type_rec_client_aux A B).
  Global Instance mapper_type_rec_client_unfold A B :
    ProtoUnfold (lsty_car (mapper_type_rec_client A B))
                (lsty_car (mapper_type_rec_client_aux A B
                           (mapper_type_rec_client A B))).
  Proof. apply proto_unfold_eq,
         (fixpoint_unfold (mapper_type_rec_client_aux A B)). Qed.

  Definition mapper_type_client : lsty Σ :=
    <!! A B> TY A → B ; mapper_type_rec_client A B.

  Definition lty_list_aux (A : ltty Σ) (X : ltty Σ) : ltty Σ :=
    (() + (A * ref_uniq X))%lty.
  Instance lty_list_aux_contractive A :
    Contractive (lty_list_aux A).
  Proof. solve_proto_contractive. Qed.
  Definition lty_list (A : ltty Σ) : ltty Σ := lty_rec (lty_list_aux A).

  Notation "'list' A" := (lty_list A) (at level 10) : lty_scope.

  Definition list_pred (A : ltty Σ) : val → val → iProp Σ :=
    (λ v w : val, ⌜v = w⌝ ∗ ltty_car A v)%I.

  Lemma llength_spec A (l : loc) :
    ⊢ {{{ ltty_car (ref_uniq (list A)) #l }}} llength #l
      {{{ xs (n:Z), RET #n; ⌜Z.of_nat (length xs) = n⌝ ∗
                            llist (λ v w , ⌜v = w⌝ ∗ ltty_car A v) l xs }}}.
  Proof.
    iIntros "!>" (Φ) "Hl HΦ".
    iLöb as "IH" forall (l Φ).
    iDestruct "Hl" as (ltmp l' [=]) "[Hl Hl']"; rewrite -H2.
    wp_lam.
    rewrite {2}/lty_list /lty_rec /lty_list_aux fixpoint_unfold.
    iDestruct "Hl'" as "[Hl' | Hl']".
    - iDestruct "Hl'" as (xs ->) "Hl'".
      wp_load. wp_pures.
      iAssert (llist (list_pred A) l [])%I with "[Hl Hl']" as "Hl".
      { rewrite /llist. iDestruct "Hl'" as %->. iApply "Hl". }
      iApply "HΦ". eauto with iFrame.
    - iDestruct "Hl'" as (xs ->) "Hl'".
      wp_load. wp_pures.
      iDestruct "Hl'" as (x vl' ->) "[HA Hl']".
      iDestruct "Hl'" as (l' xs ->) "[Hl' Hl'']".
      wp_apply ("IH" with "[Hl' Hl'']").
      { iExists _, _. iFrame "Hl' Hl''". done. }
      iIntros (ys n) "[<- H]".
      iAssert (llist (list_pred A) l (x :: ys))%I with "[Hl HA H]" as "Hl".
      { iExists x, l'. eauto with iFrame. }
      wp_pures. iApply "HΦ". iFrame "Hl". by rewrite (Nat2Z.inj_add 1).
  Qed.

  Definition send_type (A : ltty Σ) : lsty Σ :=
    (lty_select (<[1%Z := <!!> TY A ; END ]>∅))%lty.
  Definition recv_type (B : ltty Σ) : lsty Σ :=
    (<??> TY B ; END)%lty.

  Lemma mapper_rec_client_unfold_app A B :
    ⊢ mapper_type_rec_client A B <:
        (send_type A <++> (recv_type B <++> mapper_type_rec_client A B))%lty.
  Proof.
    rewrite {1}/mapper_type_rec_client /lty_rec fixpoint_unfold.
    replace (fixpoint (mapper_type_rec_client_aux A B)) with
        (mapper_type_rec_client A B) by eauto.
    rewrite /mapper_type_rec_client_aux.
    iApply lty_le_trans.
    { iApply lty_le_select_subseteq.
      apply insert_mono, insert_subseteq=> //. }
    rewrite /send_type /recv_type.
    iPoseProof (lty_le_app_select) as "[_ Hle]".
    iApply (lty_le_trans); last by iApply "Hle".
    rewrite fmap_insert fmap_empty.
    rewrite lty_app_send lty_app_end_l.
    rewrite lty_app_recv lty_app_end_l.
    iApply lty_le_refl.
  Qed.

  Lemma recv_send_swap A B :
    ⊢ (recv_type B <++> send_type A <: send_type A <++> recv_type B)%lty.
  Proof.
    iApply lty_le_trans.
    rewrite lty_app_recv lty_app_end_l.
    iApply lty_le_swap_recv_select. rewrite fmap_insert fmap_empty.
    iPoseProof (lty_le_app_select) as "[_ Hle]".
    iApply (lty_le_trans); last by iApply "Hle".
    rewrite fmap_insert fmap_empty.
    iApply lty_le_select.
    iApply big_sepM2_insert=> //.
    iSplit=> //.
    rewrite lty_app_send lty_app_end_l.
    iApply lty_le_swap_recv_send.
  Qed.

  Lemma mapper_type_rec_client_unfold_app_n A B n :
    ⊢ mapper_type_rec_client A B <:
         lty_napp (send_type A) n <++> (lty_napp (recv_type B) n <++>
                                           mapper_type_rec_client A B).
  Proof.
    iInduction n as [|n] "IH"; simpl; [ | ].
    { rewrite /send_type /recv_type /=.
      do 2 rewrite lty_app_end_l. iApply lty_le_refl. }
    rewrite -lty_napp_flip -lty_napp_flip.
    iEval (rewrite -assoc).
    iApply lty_le_trans; last first.
    { iApply lty_le_app; [ iApply lty_le_refl | ].
      iEval (rewrite -assoc assoc).
      iApply lty_le_app; [ | iApply lty_le_refl ].
      iApply napp_swap. iApply recv_send_swap. }
    iEval (rewrite -assoc).
    iApply (lty_le_trans with "IH").
    iApply lty_le_app; [ iApply lty_le_refl | ].
    iApply lty_le_app; [ iApply lty_le_refl | ].
    iApply mapper_rec_client_unfold_app.
  Qed.

  Lemma recv_send_swap_n A B n :
    ⊢ (lty_napp (recv_type B) n <++> mapper_type_rec_client A B) <:
      (send_type A <++>
                 (lty_napp (recv_type B) (S n) <++> mapper_type_rec_client A B)).
  Proof.
    iApply lty_le_trans.
    { iApply lty_le_app;
        [ iApply lty_le_refl | iApply mapper_rec_client_unfold_app ]. }
    iEval (rewrite assoc).
    iApply lty_le_trans.
    { iApply lty_le_app; [ | iApply lty_le_refl ].
      iApply napp_swap. iApply recv_send_swap. }
    iEval (rewrite -assoc (assoc _ (lty_napp _ _))).
    rewrite -lty_napp_S_r.
    iApply lty_le_refl.
  Qed.

  Lemma send_all_spec_upfront A c l xs ty :
    {{{ llist (list_pred A) l xs ∗
        c ↣ lsty_car (lty_napp (send_type A) (length xs) <++> ty) }}}
      send_all c #l
    {{{ RET #(); llist (list_pred A) l [] ∗ c ↣ lsty_car ty }}}.
  Proof.
    iIntros (Φ) "[Hl Hc] HΦ".
    iInduction xs as [|x xs] "IH".
    { wp_lam. wp_apply (lisnil_spec with "Hl"); iIntros "Hl"; wp_pures.
      iApply "HΦ". iFrame. }
    wp_lam. wp_apply (lisnil_spec with "Hl"); iIntros "Hl".
    wp_send with "[]"; first by eauto.
    rewrite lookup_total_insert.
    wp_apply (lpop_spec with "Hl"); iIntros (v) "[HIx Hl]".
    wp_send with "[HIx]".
    { iDestruct "HIx" as (->) "$HIx". }
    wp_apply ("IH" with "Hl Hc").
    done.
  Qed.

  Lemma send_all_spec_aux A B c l xs n :
    {{{ llist (list_pred A) l xs ∗
        c ↣ lsty_car (lty_napp (recv_type B) n <++> (mapper_type_rec_client A B)) }}}
      send_all c #l
    {{{ RET #(); llist (list_pred A) l [] ∗
                 c ↣ lsty_car (lty_napp (recv_type B) (length xs + n) <++>
                                             (mapper_type_rec_client A B)) }}}.
  Proof.
    iIntros (Φ) "[Hl Hc] HΦ".
    iInduction xs as [|x xs] "IH" forall (n).
    { wp_lam. wp_apply (lisnil_spec with "Hl"); iIntros "Hl"; wp_pures.
      iApply "HΦ". iFrame. }
    wp_lam. wp_apply (lisnil_spec with "Hl"); iIntros "Hl".
    simpl.
    iDestruct (iProto_mapsto_le c with "Hc []") as "Hc".
    { iApply recv_send_swap_n. }
    wp_send with "[]"; first by eauto.
    rewrite lookup_total_insert.
    wp_apply (lpop_spec with "Hl"); iIntros (v) "[HIx Hl]".
    wp_send with "[HIx]".
    { iDestruct "HIx" as (->) "$HIx". }
    wp_apply ("IH" $!(S n) with "Hl Hc").
    by rewrite Nat.add_succ_r.
  Qed.

  Lemma send_all_spec_ad_hoc A B c l xs :
    {{{ llist (list_pred A) l xs ∗
        c ↣ lsty_car (mapper_type_rec_client A B) }}}
      send_all c #l
    {{{ RET #(); llist (list_pred A) l [] ∗
                 c ↣ lsty_car (lty_napp (recv_type B) (length xs)
                                             <++> (mapper_type_rec_client A B)) }}}.
  Proof.
    iIntros (Φ) "[Hl Hc] HΦ".
    iApply (send_all_spec_aux _ _ _ _ _ 0 with "[$Hl Hc]").
    { simpl. by rewrite lty_app_end_l. }
    iIntros "!> [Hl Hc]". iApply "HΦ".
    rewrite -(plus_n_O (length xs)). iFrame.
  Qed.

  Lemma recv_all_spec B c ty l n :
    {{{ llist (list_pred B) l [] ∗
              c ↣ lsty_car (lty_napp (recv_type B) n <++> ty) }}}
      recv_all c #l #n
    {{{ ys, RET #(); ⌜n = length ys⌝ ∗
                     llist (list_pred B) l ys ∗ c ↣ lsty_car ty }}}.
  Proof.
    iIntros (Φ) "[Hl Hc] HΦ".
    iLöb as "IH" forall (n Φ).
    destruct n.
    { wp_lam. wp_pures. iApply "HΦ". by iFrame. }
    wp_lam. wp_recv (w) as "Hw". wp_pures.
    rewrite Nat2Z.inj_succ.
    replace (Z.succ (Z.of_nat (n)) - 1)%Z with (Z.of_nat (n)) by lia.
    wp_apply ("IH" with "Hl Hc").
    iIntros (ys) "(% & Hl & Hc)".
    wp_apply (lcons_spec with "[$Hl $Hw//]").
    iIntros "Hl". iApply "HΦ". iFrame.
    iPureIntro. by rewrite H1.
  Qed.

    Lemma ltyped_mapper_client_ad_hoc Γ (A B : ltty Σ) :
    ⊢ Γ ⊨ mapper_client : (A → B) ⊸
                          ref_uniq (lty_list A) ⊸
                          chan mapper_type_client ⊸
                          ref_uniq (lty_list B).
  Proof.
    iApply (ltyped_lam []).
    iApply (ltyped_lam [EnvItem "f" _]).
    iApply (ltyped_lam [EnvItem "xs" _; EnvItem "f" _]).
    iIntros (vs) "!> HΓ /=".
    rewrite (lookup_delete_ne _ "n" "c")=> //.
    rewrite (lookup_delete_ne _ "n" "xs")=> //.
    rewrite lookup_delete=> //.
    iDestruct (env_ltyped_cons _ _ "c" with "HΓ") as (vc ->) "[Hc HΓ]".
    iDestruct (env_ltyped_cons _ _ "xs" with "HΓ") as (vl ->) "[Hl HΓ]".
    iDestruct (env_ltyped_cons _ _ "f" with "HΓ") as (vf ->) "[Hf HΓ]".
    wp_send with "[Hf//]".
    iDestruct "Hl" as (l' v ->) "[Hl Hv]".
    wp_apply (llength_spec with "[Hl Hv]").
    { iExists l', v. eauto with iFrame. }
    iIntros (xs n) "[<- Hl]".
    wp_pures.
    wp_apply (send_all_spec_ad_hoc with "[$Hl $Hc]").
    iIntros "[Hl Hc]".
    wp_apply (recv_all_spec with "[Hl $Hc //]").
    iIntros (ys). iDestruct 1 as (Hlen) "[Hl Hc]".
    rewrite /mapper_type_rec_client /lty_rec fixpoint_unfold.
    wp_send with "[]"; first by eauto.
    wp_pures.
    iFrame.
    rewrite /lty_list.
    iRevert (Hlen).
    iInduction ys as [|y ys] "IH" forall (l' xs); iIntros (Hlen).
    - iExists l', NONEV. rewrite /llist. iFrame "Hl".
      iSplit=> //.
      rewrite /lty_rec. rewrite (fixpoint_unfold (lty_list_aux B)).
      iLeft. eauto.
    - iDestruct "Hl" as (vb l'') "[[-> HB] [Hl' Hrec]]".
      iExists l', _.
      iFrame "Hl'".
      iSplit=> //.
      rewrite /lty_rec.
      rewrite {2}(fixpoint_unfold (lty_list_aux B)).
      iRight. iExists _. iSplit=> //.
      iExists _, _.
      iSplit=> //.
      iFrame "HB". by iApply ("IH" with "Hrec Hc").
  Qed.

  Lemma ltyped_mapper_client_upfront Γ (A B : ltty Σ) :
    ⊢ Γ ⊨ mapper_client : (A → B) ⊸
                          ref_uniq (lty_list A) ⊸
                          chan mapper_type_client ⊸
                          ref_uniq (lty_list B).
  Proof.
    iApply (ltyped_lam []).
    iApply (ltyped_lam [EnvItem "f" _]).
    iApply (ltyped_lam [EnvItem "xs" _; EnvItem "f" _]).
    iIntros (vs) "!> HΓ /=".
    rewrite (lookup_delete_ne _ "n" "c")=> //.
    rewrite (lookup_delete_ne _ "n" "xs")=> //.
    rewrite (lookup_delete)=> //.
    iDestruct (env_ltyped_cons _ _ "c" with "HΓ") as (vc ->) "[Hc HΓ]".
    iDestruct (env_ltyped_cons _ _ "xs" with "HΓ") as (vl ->) "[Hl HΓ]".
    iDestruct (env_ltyped_cons _ _ "f" with "HΓ") as (vf ->) "[Hf HΓ]".
    wp_send with "[Hf//]".
    iDestruct "Hl" as (l' v ->) "[Hl Hv]".
    wp_apply (llength_spec with "[Hl Hv]").
    { iExists l', v. eauto with iFrame. }
    iIntros (xs n) "[<- Hl]".
    wp_pures.
    iDestruct (iProto_mapsto_le vc with "Hc []") as "Hc".
    { iApply (mapper_type_rec_client_unfold_app_n A B (length xs)). }
    wp_apply (send_all_spec_upfront with "[$Hl $Hc]").
    iIntros "[Hl Hc]".
    wp_apply (recv_all_spec with "[Hl $Hc //]").
    iIntros (ys). iDestruct 1 as (Hlen) "[Hl Hc]".
    rewrite /mapper_type_rec_client /lty_rec fixpoint_unfold.
    wp_send with "[]"; first by eauto.
    wp_pures.
    iFrame.
    rewrite /lty_list.
    iRevert (Hlen).
    iInduction ys as [|y ys] "IH" forall (l' xs); iIntros (Hlen).
    - iExists l', NONEV. rewrite /llist. iFrame "Hl".
      iSplit=> //.
      rewrite /lty_rec. rewrite (fixpoint_unfold (lty_list_aux B)).
      iLeft. eauto.
    - iDestruct "Hl" as (vb l'') "[[-> HB] [Hl' Hrec]]".
      iExists l', _.
      iFrame "Hl'".
      iSplit=> //.
      rewrite /lty_rec {2}(fixpoint_unfold (lty_list_aux B)).
      iRight. iExists _. iSplit=> //.
      iExists _, _.
      iSplit=> //.
      iFrame "HB". by iApply ("IH" with "Hrec Hc").
  Qed.

  Lemma lty_le_mapper_type_client_dual :
    ⊢ lty_dual mapper_type_service <: mapper_type_client.
  Proof.
    rewrite /mapper_type_client /mapper_type_service.
    iApply lty_le_l; [ iApply lty_le_dual_recv_exist | ]=> /=.
    iIntros (A B). iExists A,B. iModIntro.
    iLöb as "IH".
    iApply lty_le_r; [ | iApply lty_bi_le_sym; iApply lty_le_rec_unfold ].
    iApply lty_le_dual_l.
    iApply lty_le_r; [ | iApply lty_bi_le_sym; iApply lty_le_rec_unfold ].
    iApply lty_le_l; [ iApply lty_le_dual_select | ].
    iApply lty_le_branch. rewrite fmap_insert fmap_insert fmap_empty.
    iApply big_sepM2_insert=> //.
    iSplit.
    - iApply lty_le_l; [ iApply lty_le_dual_send | ].
      iApply lty_le_recv; [ iApply lty_le_refl | ].
      iApply lty_le_l; [ iApply lty_le_dual_recv | ].
      iApply lty_le_send; [ iApply lty_le_refl | ].
      iIntros "!>!>!>". iApply lty_le_dual_l. iApply "IH".
    - iApply big_sepM2_insert=> //.
      iSplit=> //. iApply lty_le_l; [ iApply lty_le_dual_end | iApply lty_le_refl ].
  Qed.

  Section with_spawn.
    Context `{!spawnG Σ}.

    Lemma ltyped_mapper_prog A B e1 e2 Γ Γ' Γ'' :
      (Γ ⊨ e2 : ref_uniq (lty_list A) ⫤ Γ') -∗
      (Γ' ⊨ e1 : (A → B) ⫤ Γ'') -∗
      Γ ⊨ par_start (mapper_service) (mapper_client e1 e2) :
        (() * (ref_uniq (lty_list B))) ⫤ Γ''.
    Proof.
      iIntros "He2 He1".
      iApply (ltyped_app with "[He2 He1]").
      { iApply (ltyped_app with "He2").
        iApply (ltyped_app with "He1").
        iApply ltyped_mapper_client_ad_hoc. }
      iApply ltyped_app.
      { iApply ltyped_mapper_service. }
      iApply ltyped_subsumption;
        [ iApply env_le_refl | | iApply env_le_refl | ].
      { iApply lty_le_arr; [ iApply lty_le_refl | ].
        iApply lty_le_arr; [ | iApply lty_le_refl ].
        iApply lty_le_arr; [ | iApply lty_le_refl ].
        iApply lty_le_chan.
        iApply lty_le_mapper_type_client_dual. }
      iApply ltyped_par_start.
    Qed.

  End with_spawn.

End mapper_example.