Require Import MyTactics.
Require Import Sequences.
Require Import LambdaCalculusSyntax.
Require Import LambdaCalculusValues.
Require Import LambdaCalculusReduction.
Require Import CPSDefinition.

(* The single-step simulation lemma in Danvy and Filinski's paper states that
   if [t1] reduces to [t2], then [cps t1 c] reduces (in one or more steps) to
   [cps t2 c]. Although this lemma is true in the pure lambda calculus, it
   fails when the calculus is extended with [Let]. This file provides two
   counter-examples. *)

(* Although Danvy and Filinski's paper does not claim that this lemma holds
   when the calculus is extended with [Let], it does not indicate that the
   lemma fails, either. *)

(* -------------------------------------------------------------------------- *)

(* The tactic [analyze] assumes that there is a hypothesis [star cbv t1 t2].
   It checks that [t1] and [t2] are distinct and, if [t1] reduces to [t'1],
   updates this hypothesis to [star cbv t'1 t2]. Repeating this tactic allows
   proving that [t1] does *not* reduce to [t2]. *)

Ltac analyze :=
  invert_star_cbv; repeat invert_cbv; compute in *; fold cbv_mask in *;
  repeat match goal with h: True |- _ => clear h end.

Transparent cps cpsv. (* required by [compute] *)

(* -------------------------------------------------------------------------- *)

(* Consider the term [t1], defined as follows. In informal syntax, [t1]
   is written (\z.let w = z in w) (\x.x). *)

Definition t1 :=
  App (Lam (Let (Var 0) (Var 0))) (Lam (Var 0)).

(* The term [t1] reduces to [t2], which in informal syntax is written
   let w = \x.x in w. *)

Definition t2 :=
  Let (Lam (Var 0)) (Var 0).

Goal
  cbv t1 t2.
Proof.
  unfold t1, t2. obvious.
Qed.

(* The single-step simulation diagram is violated: [cps t1 init] does
   *not* reduce (in any number of steps) to [cps t2 init]. *)

Goal
  ~ (star cbv (cps t1 init) (cps t2 init)).
Proof.
  compute; fold cbv_mask. intro.
  analyze.
  analyze.
  (* This point is the near miss:
     [cps t1 init] has now reduced to a [Let] construct, whereas
     [cps t2 init] is a similar-looking [Let] construct.
     Both have the same value on the left-hand side of the [Let].
     But the right-hand sides of the [Let] construct differ. *)
  analyze.
  analyze.
  analyze.
Qed.

(* Let us summarize.

   The term [t1] reduces in one step to [t2] as follows:

     (\z.let w = z in w) (\x.x)
     ->
     let w = \x.x in w

   The term [cps t1 init], in informal notation, is as follows:

     (\z.\k.let w = z in k w)
     (\x.\k. k x)
     (\w.w)

   This term reduces in two steps to:

     let w = \x.\k. k x in
     (\w.w) w

   But the term [cps t2 init], in informal notation, is:

     let w = \x.\k. k x in
     w

   This is our near miss. Both terms are [let] constructs and both have
   the same left-hand side, but the right-hand sides differ by a beta-v
   reduction. Thus, [cps t1 init] does not reduce *in call-by-value* to
   [cps t2 init]. In order to allow [cps u1 init] to join [cps u2 init],
   we must allow beta-v reductions in the right-hand side of [let]
   constructs (and, it turns out, under lambda-abstractions, too.)
   This is visible in the proof of the [simulation] lemma in the file
   CPSSimulation: there, we use the reduction strategy [pcbv], which
   allows (parallel) beta-v reductions under arbitrary contexts. *)

(* This counter-example is one of two closed counter-examples of minimal size.
   It has size 4 (counting [Lam], [App], and [Let] nodes) and involves only
   one [Let] construct. There are no smaller counter-examples. An exhaustive
   search procedure, coded in OCaml, was used to find it. *)