Set Implicit Arguments.
Opaque plus. (* avoid pesky reductions *)
(* ---------------------------------------------------------------------------- *)
(* Environments map variables to data. *)
(* Environments are homogeneous -- there is only one kind of variables -- and non-dependent -- variables do not occur within data. *)
(* What should be the type of an environment? Multiple answers come to mind, such as [list A], [nat -> option A], [list (option A)], etc. The choice of a definition is guided by the desire that [lookup] and [insert] be total functions that satisfy a number of natural laws. In particular, if [insert] is a total function, then we cannot expect the domain of an environment to be contiguous. This rules out [list A]. Representing an environment by its lookup function, of type [nat -> option A], works, but does not allow defining [fold] over environments. In the end, representing an environment by a finite list of possibly-null entries seems to be a reasonable choice. *)
Definition env A :=
list (option A).
(* What notion of equality over environments do we need? Extensional equality of the [lookup] functions seems to be the most natural notion. The predicate [agree], defined below, is a bounded version of this notion. Perhaps by chance, the basic laws that relate [lookup], [insert], and [map] are valid with respect to Leibniz equality, so we do not define extensional equality. *)
(* ---------------------------------------------------------------------------- *)
(* Operations on environments. *)
(* The empty environment is undefined everywhere. *)
Definition empty A : env A :=
nil.
(* Environment lookup. *)
Fixpoint lookup A (x : nat) (e : env A) : option A :=
match e, x with
| nil, _ =>
None
| entry :: _, 0 =>
entry
| _ :: e, S x =>
lookup x e
end.
(* [insert x a e] inserts a new variable [x], associated with data [a], in the environment [e]. The pre-existing environment entries at index [x] and above are shifted up. Thus, [insert x] is closely analogous to [shift x] for terms. *)
(* [insert] inserts a non-null entry in the environment. We define it in terms of [raw_insert], which can also be used to insert a null entry. [raw_insert] is useful because it allows generating every environment (thus, it can be used in the formulation of an induction principle). *)
Fixpoint raw_insert A (x : nat) (o : option A) (e : env A) : env A :=
match x, e with
| 0, _ =>
o :: e
| S x, entry :: e =>
entry :: raw_insert x o e
| S x, nil =>
None :: raw_insert x o e
end.
Notation insert x a e :=
(raw_insert x (Some a) e).
(* [map f e] is the environment obtained by applying [f] to every datum in the environment [e]. *)
Fixpoint map A B (f : A -> B) (e : env A) :=
match e with
| nil =>
nil
| None :: e =>
None :: map f e
| Some a :: e =>
Some (f a) :: map f e
end.
(* [fold f e accu] performs an iteration over all entries in the environment. Older entries are visited first: in other words, the initial accumulator should make sense at the toplevel, outside of the environment, and is pushed successively into every binding, so as to yield a final accumulator that makes sense inside this environment. *)
Fixpoint fold A B (f : option A -> B -> B) (e : env A) (accu : B) : B :=
match e with
| nil =>
accu
| o :: e =>
f o (fold f e accu)
end.
(* ---------------------------------------------------------------------------- *)
(* Basic arithmetic simplifications. *)

Lemma one_plus_x_minus_one_left:
forall x,
(1 + x) - 1 = x.