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

Lemma subst_lc_c_alternate_proof : forall (x : atom) u e,
lc_c e ->
lc_c u ->
lc_c ([x ~> u] e).