4. Define a simple programming language, its semantics, and its typing rules, and then prove that well-typed programs cannot go wrong. Specifically:

(a) Define var as a synonym for the natural numbers.

(b) Define an inductive type exp of expressions, containing natural number constants, natural number addition, pairing of two other expressions, extraction of the first component of a pair, extraction of the second component of a pair, and variables (based on the var type you defined).

(c) Define an inductive type cmd of commands, containing expressions and variable assignments. A variable assignment node should contain the variable being as- signed, the expression being assigned to it, and the command to run afterward.

(d) Define an inductive type val of values, containing natural number constants and pairings of values.

(e) Define a type of variable assignments, which assign a value to each variable.

(f) Define a big-step evaluation relation eval, capturing what it means for an ex- pression to evaluate to a value under a particular variable assignment. “Big step” means that the evaluation of every expression should be proved with a single instance of the inductive predicate you will define. For instance, “1 + 1 evaluates to 2 under assignment va” should be derivable for any assignment va.

(g) Define a big-step evaluation relation run, capturing what it means for a command to run to a value under a particular variable assignment. The value of a command is the result of evaluating its final expression.

(h) Define a type of variable typings, which are like variable assignments, but map variables to types instead of values. You might use polymorphism to share some code with your variable assignments.

(i) Define typing judgments for expressions, values, and commands. The expression and command cases will be in terms of a typing assignment.

(j) Define a predicate varsType to express when a variable assignment and a variable typing agree on the types of variables.

(k) Prove that any expression that has type t under variable typing vt evaluates under variable assignment va to some value that also has type t in vt, as long as va and vt agree.

(l) Prove that any command that has type t under variable typing vt evaluates under variable assignment va to some value that also has type t in vt, as long as va and vt agree.

A few hints that may be helpful:
(a) One easy way of defining variable assignments and typings is to define both as instances of a polymorphic map type. The map type at parameter T can be defined to be the type of arbitrary functions from variables to T. A helpful function for implementing insertion into such a functional map is eq_nat_dec, which you can make available with
Require Import Arith.
eq_nat_dec has a dependent type that tells you that it makes accurate decisions on whether two natural numbers are equal, but you can use it as if it returned a boolean, e.g.,
if eq_nat_dec n m then E1 else E2.
(b) If you follow the last hint, you may find yourself writing a proof that involves an expression with eq_nat_dec that you would like to simplify. Running destruct on the particular call to eq_nat_dec should do the trick. You can automate this advice with a piece of Ltac:
match goal with
| [ ` context[eq_nat_dec ?X ?Y ] ] ⇒ destruct (eq_nat_dec X Y )
(c) You probably do not want to use an inductive definition for compatibility of variable assignments and typings.

(d) The CpdtTactics module from this book contains a variant crush’ of crush. crush’ takes two arguments. The first argument is a list of lemmas and other functions to be tried automatically in “forward reasoning” style, where we add new facts without being sure yet that they link into a proof of the conclusion. The second argument is a list of predicates on which inversion should be attempted automatically. For instance, running
  crush’ (lemma1, lemma2 ) pred
will search for chances to apply lemma1 and lemma2 to hypotheses that are already available, adding the new concluded fact if suitable hypotheses can be found. Inversion will be attempted on any hypothesis using pred, but only those inversions that narrow the field of possibilities to one possible rule will be kept. The format of the list arguments to crush’ is that you can pass an empty list as tt, a singleton list as the unadorned single element, and a multiple-element list as a tuple of the elements.

(e) If you want crush’ to apply polymorphic lemmas, you may have to do a little extra work, if the type parameter is not a free variable of your proof context (so that crush’ does not know to try it). For instance, if you define a polymorphic map insert function assign of some type ∀ T : Set, ..., and you want particular applications of assign added automatically with type parameter U, you would need to include assign in the lemma list as assign U (if you have implicit arguments off) or assign (T := U ) or @assign U (if you have implicit arguments on).