(** A nominal representation of STLC terms.  This version looks a lot like we expect a representation of the lambda calculus to look like. Unlike the LN version, the syntax does not distinguish between bound and free variables and abstractions include the name of binding variables.  *)
(*************************************************************)
(** * Imports                                                *)
(*************************************************************)
(** Some of our proofs are by induction on the *size* of terms. We'll use Coq's [omega] tactic to easily dispatch reasoning about natural numbers. *)
Require Export Omega.
(** We will use the [atom] type from the metatheory library to represent variable names. *)
Require Export Metalib.Metatheory.
(** Although we are not using LNgen, some of the tactics from its library are useful for automating reasoning about names.  *)
Require Export Metalib.LibLNgen.
(** Some fresh variables *)
Notation X := (fresh nil).
Notation Y := (fresh (X :: nil)).
Notation Z := (fresh (X :: Y :: nil)).
(*************************************************************)
(** * A nominal representation of terms                      *)
(*************************************************************)
Inductive n_exp : Set :=
| n_var (x:atom)
| n_abs (x:atom) (t:n_exp)
| n_app (t1:n_exp) (t2:n_exp).
(** For example, we can encode the expression [(\X.Y X)] as below.  *)
Definition demo_rep1 := n_abs X (n_app (n_var Y) (n_var X)).
(** For example, we can encode the expression [(\Z.Y Z)] as below.  *)
Definition demo_rep2 := n_abs Z (n_app (n_var Y) (n_var Z)).
(** As usual, the free variable function needs to remove the bound variable in the [n_abs] case. *)
Fixpoint fv_nom (n : n_exp) : atoms :=
match n with
| n_var x => {{x}}
| n_abs x n => remove x (fv_nom n)
| n_app t1 t2 => fv_nom t1 `union` fv_nom t2
end.
(** The tactics for reasoning about lists and sets of atoms are useful here too. *)
Example fv_nom_rep1 : fv_nom demo_rep1 [=] {{ Y }}.
(** What makes this a *nominal* representation is that our operations are based on the following swapping function for names.  Note that this operation is symmetric: [x] becomes [y] and [y] becomes [x]. *)
Definition swap_var (x:atom) (y:atom) (z:atom) :=
if (z == x) then y else if (z == y) then x else z.
(** The main insight of nominal representations is that we can rename variables, without capture, using a simple structural induction. Note how in the [n_abs] case we swap all variables, both bound and free.  For example:  (swap x y) (\z. (x y)) = \z. (y x))  (swap x y) (\x. x) = \y.y  (swap x y) (\y. x) = \x.y  *)
Fixpoint swap (x:atom) (y:atom) (t:n_exp) : n_exp :=
match t with
| n_var z     => n_var (swap_var x y z)
| n_abs z t1  => n_abs (swap_var x y z) (swap x y t1)
| n_app t1 t2 => n_app (swap x y t1) (swap x y t2)
end.
(** Because swapping is a simple, structurally recursive function, it is highly automatable using the [default_simp] tactic from LNgen library.  This tactic "simplifies" goals using a combination of common proof steps, including case analysis of all disjoint sums in the goal. Because atom equality returns a sumbool, this makes this tactic useful for reasoning by cases about atoms.  For more information about the [default_simp] tactic, see [metalib/LibDefaultSimp.v].  WARNING: this tactic is not always safe. It's a power tool and can put your proof in an irrecoverable state. *)
Example swap1 : forall x y z, x <> z -> y <> z ->
swap x y (n_abs z (n_app (n_var x)(n_var y))) = n_abs z (n_app (n_var y) (n_var x)).
Example swap2 : forall x y, x <> y ->
swap x y (n_abs x (n_var x)) = n_abs y (n_var y).
Example swap3 : forall x y, x <> y ->
swap x y (n_abs y (n_var x)) = n_abs x (n_var y).
(** We define the "alpha-equivalence" relation that declares when two nominal terms are equivalent (up to renaming of bound variables).  Note the two different rules for [n_abs]: either the binders are the same and we can directly compare the bodies, or the binders differ, but we can swap one side to make it look like the other.  *)
Inductive aeq : n_exp -> n_exp -> Prop :=
| aeq_var : forall x,
aeq (n_var x) (n_var x)
| aeq_abs_same : forall x t1 t2,
aeq t1 t2 -> aeq (n_abs x t1) (n_abs x t2)
| aeq_abs_diff : forall x y t1 t2,
x <> y ->
x `notin` fv_nom t2 ->
aeq t1 (swap y x t2) ->
aeq (n_abs x t1) (n_abs y t2)
| aeq_app : forall t1 t2 t1' t2',
aeq t1 t1' -> aeq t2 t2' ->
aeq (n_app t1 t2) (n_app t1' t2').
Hint Constructors aeq : core.
Example aeq1 : forall x y, x <> y -> aeq (n_abs x (n_var x)) (n_abs y (n_var y)).
(*************************************************************)
(** ** Properties about swapping                             *)
(*************************************************************)
(** Now let's look at some simple properties of swapping. *)
(** Demo: We will need the next two properties later in the tutorial, so we show that even though there are many cases to consider, [default_simp] can find these proofs. *)
(*************************************************************)
(** ** Exercises                                             *)
(*************************************************************)
(** *** Recommended Exercise: [swap] properties  Prove the following properties about swapping, either explicitly (by destructing [x == y]) or automatically (using [default_simp]).  *)

(** *** Challenge exercise: equivariance  Equivariance is the property that all functions and relations are preserved under swapping. (Hint: [default_simp] will be slow on some of these properties, and sometimes won't be able to do them automatically.)  *)
Lemma swap_var_equivariance : forall v x y z w,
swap_var x y (swap_var z w v) =
swap_var (swap_var x y z) (swap_var x y w) (swap_var x y v).