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

Lemma uniq_demo : forall (x y : atom) (T : typ),
x <> y -> uniq ((x ~ T) ++ (y ~ T)).