Section CofiniteQuantification.
Local Notation "{ k ~> u } t" := (open_rec k u t) (at level 67).
(* With this new definition, we can almost use the same proof for [open_rec_lc], we only need one change. We need to add the line [pick fresh x for L.]  immediately before the use of [apply open_rec_lc_core].  This tactic, defined in [Metatheory], introduces a new atom [x] that is known not to be in the set [L]. *)
(* Take-home Exercise: The next two lemmas have exactly the same proofs as before. *)
(* Exercise [subst_lc_c]:  Once we have changed the definition of lc, we can complete the proof of subst_lc.  HINT: apply lc_abs_c with cofinite set (L `union` singleton x). This gives us an atom x0, and a hypothesis that x0 is fresh for both L and x. *)
End CofiniteQuantification.
(*************************************************************************)
(** * Tactic support *)
(*************************************************************************)
(** When picking a fresh atom or applying a rule that uses cofinite quantification, choosing a set of atoms to be fresh for can be tedious.  In practice, it is simpler to use a tactic to choose the set to be as large as possible.  The tactic [gather_atoms] is used to collect together all the atoms in the context.  It relies on an auxiliary tactic, [gather_atoms_with] (from MetatheoryAtom), which maps a function that returns a finite set of atoms over all hypotheses with the appropriate type. *)
Ltac gather_atoms ::=
let A := gather_atoms_with (fun x : atoms => x) in
let B := gather_atoms_with (fun x : atom => singleton x) in
let C := gather_atoms_with (fun x : list (atom * typ) => dom x) in
let D := gather_atoms_with (fun x : exp => fv x) in
constr:(A `union` B `union` C `union` D).
(** A number of other, useful tactics are defined by the Metatheory library, and each depends on [gather_atoms].  By redefining [gather_atoms], denoted by the [::=] in its definition below, we automatically update these tactics so that they use the proper notion of "all atoms in the context."  For example, the tactic [(pick fresh x)] chooses an atom fresh for "everything" in the context.  It is the same as [(pick fresh x for L)], except where [L] has been computed by [gather_atoms].  The tactic [(pick fresh x and apply H)] applies a rule [H] that is defined using cofinite quantification.  It automatically instantiates the finite set of atoms to exclude using [gather_atoms]. *)
(** *** Example  Below, we reprove [subst_lc_c] using [(pick fresh and apply)]. Step through the proof below to see how [(pick fresh and apply)] works. *)
(*************************************************************************)
(*                                                                       *)
(*  Coffee break                                                         *)
(*                                                                       *)
(*************************************************************************)
(*************************************************************************)
(** * Typing environments *)
(*************************************************************************)
(** We represent environments as association lists (lists of pairs of keys and values) whose keys are [atom]s. *)
Notation env := (list (atom * typ)).
(** For STLC, environments bind [atom]s to [typ]s.  We define an abbreviation [env] for the type of these environments.  Coq will print [list (atom * typ)] as [env], and we can use [env] as a shorthand for writing [list (atom * typ)].  Lists are defined in Coq's standard library, with the constructors [nil] and [cons].  The list library includes the [::] notation for cons as well as standard list operations such as append, map, and fold. The infix operation "++" is list append.  The Metatheory library extends this reasoning by instantiating the AssocList library to provide support for association lists whose keys are [atom]s.  Everything in this library is polymorphic over the type of objects bound in the environment.  Look in AssocList for additional details about the functions and predicates that we mention below. *)
(** Environment equality *)
(** When reasoning about environments, we often need to talk about bindings in the "middle" of an environment. Therefore, it is common for lemmas and definitions to use list append in their statements. Unfortunately, list append is associative, so two Coq expressions may denote the same environment even though they are not equal.  The tactic [simpl_env] reassociates all concatenations of environments to the right. *)
(** To make environments easy to read, instead of building them from [nil] and [cons], we prefer to build them from the following components: - [nil]: The empty list. - [one]: Lists consisting of exactly one item. - [++]:  List append.  Furthermore, we introduce compact notation for one (singleton lists): [(x ~ T)] is the same as [one (x, T)]. *)
(** The simpl_env tactic actually puts lists built from only nil, one and [++] into a "normal form". This process reassociates all appends to the right, removes extraneous nils converts cons to singleton lists with an append. *)
(** Note that the [simpl] tactic doesn't produce the "normal form" for environments. It should always be followed up with [simpl_env].  Furthermore, to convert an environment to any equivalent form other than the normal form (perhaps to apply a lemma) use the tactic [rewrite_env]. *)
(** Environment operations. *)
(** The ternary predicate [binds] holds when a given binding is present somewhere in an environment. *)
(** The function [dom] computes the domain of an environment, returning a finite set of [atom]s. Note that we cannot use Coq's equality for finite sets, we must instead use a defined relation [=] for atom set equality. *)
(** The unary predicate [uniq] holds when each atom is bound at most once in an environment. *)
(*************************************************************************)
(** * Typing relation *)
(*************************************************************************)
(** The definition of the typing relation is straightforward.  In order to ensure that the relation holds for only well-formed environments, we check in the [typing_var] case that the environment is [uniq].  The structure of typing derivations implicitly ensures that the relation holds only for locally closed expressions.  Finally, note the use of cofinite quantification in the [typing_abs] case. *)
Inductive typing_c : env -> exp -> typ -> Prop :=
| typing_var_c : forall E (x : atom) T,
uniq E ->
binds x T E ->
typing_c E (fvar x) T
| typing_abs_c : forall (L : atoms) E e T1 T2,
(forall (x:atom), x `notin` L ->
typing_c ((x ~ T1) ++ E) (open e x) T2) ->
typing_c E (abs T1 e) (typ_arrow T1 T2)
| typing_app_c : forall E e1 e2 T1 T2,
typing_c E e1 (typ_arrow T1 T2) ->
typing_c E e2 T1 ->
typing_c E (app e1 e2) T2.
(** We add the constructors of the typing relation as hints to be used by the [auto] and [eauto] tactics. *)
Hint Constructors typing_c : core.
(*************************************************************************)
(** * Weakening *)
(*************************************************************************)
(** Weakening states that if an expression is typeable in some environment, then it is typeable in any well-formed extension of that environment.  This property is needed to prove the substitution lemma.  As stated below, this lemma is not directly proveable.  The natural way to try proving this lemma proceeds by induction on the typing derivation for [e]. *)
(** We are stuck in the [typing_abs_c] case because the induction hypothesis [H0] applies only when we weaken the environment at its head.  In this case, however, we need to weaken the environment in the middle; compare the conclusion at the point where we're stuck to the hypothesis [H], which comes from the given typing derivation.  We can obtain a more useful induction hypothesis by changing the statement to insert new bindings into the middle of the environment, instead of at the head.  However, the proof still gets stuck, as can be seen by examining each of the cases in the proof below.  Note: To view subgoal n in a proof, use the command "[Show n]". To work on subgoal n instead of the first one, use the command "[Focus n]". *)
(** The hypotheses in the [typing_var_c] case include an environment [E0] that that has no relation to what we need to prove.  The missing fact we need is that [E0 = (G ++ E)].  The problem here arises from the fact that Coq's [induction] tactic let's us only prove something about all typing derivations. While it's clear to us that weakening applies to all typing derivations, it's not clear to Coq, because the environment is written using concatenation.  The [induction] tactic expects that all arguments to a judgement are variables.  So we see [E0] in the proof instead of [(G ++ E)].  The solution is to restate the lemma.  For example, we can prove  << forall E F E' e T, typing_c E' e T -> forall G, E' = G ++ E -> uniq (G ++ F ++ E) -> typing_c (G ++ F ++ E) e T. >>  The equality gets around the problem with Coq's [induction] tactic.  The placement of the [(forall G)] quantifier gives us a sufficiently strong induction hypothesis in the [typing_abs_c] case.  However, we prefer not to state the lemma in the way shown above, since it is not as readable as the original statement.  Instead, we use a tactic to introduce the equality within the proof itself. The tactic [(remember t as t')] replaces an object [t] with the identifier [t'] everywhere in the goal and introduces an equality [t' = t] into the context.  It is often combined with [generalize dependent], as illustrated below. *)
(** *** Exercise  See how we use [remember as] in the proof below for weakening. Then, complete the proof below.  HINTS:  - The [typing_var_c] case follows from [binds_weaken], the weakening lemma for the [binds] relation.  - The [typing_abs_c] case follows from the induction hypothesis, but the [apply] tactic may be unable to unify things as you might expect.  -- Recall the [pick fresh and apply] tactic.  -- In order to apply the induction hypothesis, use [rewrite_env] to reassociate the list operations.  -- After applying the induction hypothesis, use [simpl_env] to use [uniq_push].  -- Here, use [auto] to solve facts about finite sets of atoms, since it will simplify the [dom] function behind the scenes.  [fsetdec] does not work with the [dom] function.  - The [typing_app_c] case follows directly from the induction hypotheses. *)

Lemma typing_c_weakening_strengthened :  forall (E F G : env) e T,
typing_c (G ++ E) e T ->
uniq (G ++ F ++ E) ->
typing_c (G ++ F ++ E) e T.