(** Homework Assignment 2#
#
##Interactive Computer Theorem
Proving#
#
CS294-9, Fall 2006#
#
UC Berkeley *)
(** Submit your solution file for this assignment as an attachment
##by e-mail## with
the subject "ICTP HW2" by the start of class on September 14.
You should write your solutions entirely on your own.
##Template source file##
*)
Require Import Arith Omega.
(** Some useful modules for dealing with arithmetic *)
(** Here's a list of tactics that should be sufficient to solve the problems in
this assignment. You're free to use any tactics except in sections that give
explicit restrictions, though. In particular, all of that stuff you did
manually in HW1 should be left to automation now, through liberal use of
tactics like [intuition] (described below). Whenever a goal has some
non-trivial propositional structure, starting off with [intuition] (possibly
following a named [intros], though [intuition] subsumes [intros] without
arguments) is a sound idea.
More detailed information on tactics can be found in
##the manual's tactic index##.
Basic goal manipulation:
- [intros]:
Transform a goal with implications and/or universal quantifications into a
simpler goal, where the assumptions and quantified variables have moved
into the assumptions above the double line. [intros] with no arguments
performs this for all implications and [forall]s visible in the goal, while
[intros] followed by a space-separated list of names performs this
simplification as many times as there are names, using your list to pick the
names of new assumptions instead of assigning them itself. You'll often
want to use the named form of [intros] for goals involving negation and
other syntactic abbreviations for implications. (Remember that [~P] is
defined as [P -> False].)
- [apply name]:
Apply the named lemma/theorem [name] to the current goal, adding the
premises of that lemma as subgoals, if there are any premises.
- [change e]:
When the goal is some term [e'], switch to proving [e] instead. [e] and
[e'] must be equivalent through the computational reductions.
Equality:
- [reflexivity]:
Prove any equality between two equivalent terms.
- [rewrite H]:
Rewrite the goal using the equality proved by hypothesis [H]. You can also
use [rewrite H1 in H2] to do the rewriting in [H2], and you can follow
[rewrite] with [<-] to use the equality to rewrite from right to left
instead of left to right.
Simplification:
- [simpl]:
Try to simplify the goal using computational reduction rules. You may also
use [simpl in H] to simplify hypothesis [H] or [simpl in *] to simplify the
goal and all hypotheses.
- [unfold name]:
Where [name] is a symbol assigned a meaning earlier in a [Definition],
expand all occurrences of that name in the goal into the definition. The
same alternate syntaxes that work with [simpl] apply here, too.
When considering using [unfold], keep in mind that it can sometimes make
the goal/hypothesis ##more## complicated than it was before.
[simpl] is generally better about avoiding that kind of thing.
Working with inductive types:
- [destruct e]:
Perform a case analysis on the expression [e] of inductive type. The goal
is split into one subgoal per constructor of [e]'s type, with [e] replaced
by a generic application of that constructor to fresh variables. If [e] is
the name of a variable bound in the goal rather than a variable bound in the
assumptions above the double line, then [intros] is used automatically to
move that variable and those that come before it above the double line.
- [induction e]:
Like [destruct e], but the subgoals also have additional assumptions
corresponding to inductive hypotheses where appropriate. You may also use
[induction] on variables quantified in the goal.
- [discriminate]:
Prove any formula, given that your assumptions include an equality between
terms built with different constructors of an inductive type.
- [injection H]:
Where [H] is a hypothesis equating two values built with the same
constructor of an inductive type, add all of the equalities that this
relationship applies.
Automation:
- [trivial]:
Prove a very obvious goal. The best way to get a handle on what this means
is through experience.
- [tauto]:
Prove any propositional tautology. Remember that we're working in
constructive logic, so not every formula that can be proved "true" using
truth tables is actually true constructively!
- [intuition]:
A version of [tauto] that lets you take advantage of partial success.
In particular, when it can't prove the goal completely, it will often split
it into a set of simpler subgoals based on properties of the logical
connectives.
- [firstorder]:
Version of [intuition] that has a limited ability to automate proofs in
first-order logic
- [congruence]:
Prove a goal whose truth follows from the definition of equality and basic
properties of inductive type constructors.
- [omega]:
Prove any goal that follows from properties of linear arithmetic.
For some of the theorems in this assignment, it will be very useful to use
some of the ways of ##combining## tactics.
- The notation [t1; t2] means "run [t1] on the current goal and then run [t2]
on ##all## of the subgoals generated by [t1]."
- The notation [try t] means "try running [t1] on the goal; if it fails,
silently roll back to where we started instead of signaling an error to the
user."
Finally, don't forget these other general theorem proving tools:
- You can introduce a definition with [Definition name := e.].
- When you want to define a recursive function, it's easiest to use
the [Fixpoint] command.
- Often a theorem will be easiest to prove if you prove some lemmas first.
These are introduced with [Lemma name : formula.], followed by a proof
script and [Qed.], just as for [Theorem]. (In fact, [Theorem] and [Lemma]
are synonyms, with the choice of which to use serving only as
documentation.)
*)
(** * Booleans *)
(** ** Some boolean functions *)
(** Fill in some definitions for the following functions over [bool]s.
We saw the definition of this standard type in lecture.
I hope that the names of the functions convey their intended semantics.
*)
Definition bool_not : bool -> bool.
Admitted.
Definition bool_xor : bool -> bool -> bool.
Admitted.
Definition bool_and : bool -> bool -> bool.
Admitted.
Definition bool_or : bool -> bool -> bool.
Admitted.
Definition bool_eq : bool -> bool -> bool.
Admitted.
(** ** Correctness properties *)
(** Prove these "obvious" theorems about your definitions. *)
Theorem xor_not_eq : forall b1 b2,
bool_xor b1 b2 = bool_not (bool_eq b1 b2).
Admitted.
Theorem not_and_or_not : forall b1 b2,
bool_not (bool_and b1 b2) = bool_or (bool_not b1) (bool_not b2).
Admitted.
(** * Lists *)
(** Here's our old friend the list of natural numbers. *)
Inductive list : Set :=
| nil : list
| cons : nat -> list -> list.
(** And here's the list append function from HW0. *)
Fixpoint append (ls1 ls2 : list) {struct ls1} : list :=
match ls1 with
| nil => ls2
| cons h t => cons h (append t ls2)
end.
(** This is the most straightforward definition of list reverse. *)
Fixpoint reverse (ls : list) : list :=
match ls with
| nil => nil
| cons h t => append (reverse t) (cons h nil)
end.
(** Unfortunately, it has quadratic running time, so we'd like something better.
The next two definitions provide a linear-time implementation.
*)
Fixpoint reverse'_helper (ls acc : list) {struct ls} : list :=
match ls with
| nil => acc
| cons h t => reverse'_helper t (cons h acc)
end.
Definition reverse' (ls : list) : list := reverse'_helper ls nil.
(** Here's a copy of the [append] associativity proof from HW0.
You'll probably want to use it somewhere....
*)
Theorem append_associative : forall ls1 ls2 ls3,
append (append ls1 ls2) ls3 = append ls1 (append ls2 ls3).
induction ls1; simpl; intuition.
rewrite IHls1; trivial.
Qed.
(** ** Equivalence of the two [reverse] definitions *)
(** Prove this theorem, showing that the faster [reverse'] produces identical
outputs to the more clearly correct [reverse].
*)
Theorem reverse'_correct : forall ls, reverse' ls = reverse ls.
Admitted.
(** * Trees *)
(** Here's the definition of binary trees of natural numbers from lecture. *)
Inductive tree : Set :=
| Leaf : tree
| Node : tree -> nat -> tree -> tree.
(** ** Sanity checks on properties of constructors *)
(** For the next two theorems, to make things interesting, you can only use
these tactics:
- [change]
- [intros]
- [reflexivity]
- [rewrite]
- [trivial]
And you might find it helpful to create a [Definition] or two.
*)
Theorem Leaf_neq_Node : forall t1 n t2, Leaf <> Node t1 n t2.
Admitted.
Theorem Node_inj : forall t11 n1 t12 t21 n2 t22,
Node t11 n1 t12 = Node t21 n2 t22
-> t11 = t21.
Admitted.
(** ** Inserting nodes and preserving ordering *)
(** Here's a standard function for insertion into a binary tree.
There are two new things here.
First, we have a use of the [le_lt_dec] function from the [Arith] library.
For the purposes of this assignment, you can treat it as a fancy version of
a standard [<=] operator.
Second, we have an [if] expression. The [if] below is just syntactic sugar
for: [[
match le_lt_dec n n' with
| true => Node (insert n tle) n' tgt
| false => Node tle n' (insert n tgt)
end
]]
(Actually, [le_lt_dec] returns a fancier type than [bool], but that doesn't
matter here.)
The right way to simplify an [if] is to perform a case analysis (i.e., the
[destruct] tactic) on its test expression.
*)
Fixpoint insert (n : nat) (t : tree) {struct t} : tree :=
match t with
| Leaf => Node Leaf n Leaf
| Node tle n' tgt =>
if le_lt_dec n n'
then Node (insert n tle) n' tgt
else Node tle n' (insert n tgt)
end.
(** Here's a function to count the number of internal nodes in a tree. *)
Fixpoint size (t : tree) : nat :=
match t with
| Leaf => 0
| Node tle _ tgt => S (size tle + size tgt)
end.
(** ** Insertion increases size *)
(** Prove this theorem: *)
Theorem insert_increases : forall (n : nat) (t : tree),
size (insert n t) = S (size t).
Admitted.
(** ** Insertion preserves ordering *)
(** We define what it means for a tree to be a binary search tree where
all values fall into a particular range of natural numbers.
Here [option nat] is a type that we haven't seen before.
It's the type of optional natural numbers.
A value of that type is either [None] or [Some n] for some natural number [n].
Options are inductive types that can be used via pattern matching like usual.
The other thing that might be surprising here is that we are defining a
recursive function that returns logical propositions, written as [Prop].
Besides the initial novelty of the idea, there's nothing different here from
writing recursive functions that return normal types.
*)
Fixpoint ordered' (min : nat) (max : option nat) (t : tree) {struct t} : Prop :=
match t with
| Leaf => True
| Node t1 n t2 =>
min <= n
/\ match max with
| None => True
| Some max => n <= max
end
/\ ordered' min (Some n) t1
/\ ordered' (S n) max t2
end.
(** We use [ordered'] to define what it means for a tree to be a BST in
general.
*)
Definition ordered (t : tree) : Prop :=
ordered' O None t.
(** Prove this theorem: *)
Theorem insert_ordered : forall (n : nat) (t : tree),
ordered t -> ordered (insert n t).
Admitted.
(** * Expression evaluation *)
(** In this section, we'll define a tiny "programming language" of arithmetic
expressions, write some "compiler optimizations" for it, and prove that our
optimizations are sound. *)
(** We represent variables as natural numbers. Any infinite [Set] would do. *)
Definition var := nat.
(** Expressions are built from constants, variables, addition, and
multiplication. *)
Inductive exp : Set :=
| Const : nat -> exp
| Var : var -> exp
| Plus : exp -> exp -> exp
| Times : exp -> exp -> exp.
(** Here's the meaning of expressions.
The inputs to the fucnction are the expression to evaluate and an environment
giving values to the variables.
The environment is just a function from variables to natural numbers.
*)
Fixpoint eval (e : exp) (ctx : var -> nat) {struct e} : nat :=
match e with
| Const n => n
| Var x => ctx x
| Plus e1 e2 => eval e1 ctx + eval e2 ctx
| Times e1 e2 => eval e1 ctx * eval e2 ctx
end.
(** ** Constant folding *)
(** Expressions may contain some pieces without variables that can be evaluated
"once and for all." This is called "constant folding" in the world of
compilers. *)
(** This function implements a check to see if an expression has any
opportunities for constant folding. It returns a proposition equivalent to
the statement that the input expression has no more places to simplify by
constant folding. *)
Fixpoint constant_folded (e : exp) : Prop :=
match e with
| Const _ => True
| Var _ => True
| Plus (Const _) (Const _) => False
| Plus e1 e2 => constant_folded e1 /\ constant_folded e2
| Times (Const _) (Const _) => False
| Times e1 e2 => constant_folded e1 /\ constant_folded e2
end.
(** Define a function that performs all possible constant foldings in an
expression.
*)
Definition constant_fold : exp -> exp.
Admitted.
(** Prove that your function doesn't miss any opportunities to simplify. *)
(** On this and the rest of the problems on this assignment, you'll probably
find it very helpful to use the [t1; t2] and [try t] tactic notations
mentioned at the start of this document. Otherwise, you'll be doing a lot of
typing, since you'll probably end up with many very similar cases to consider.
A general pattern is [t1; try t2; try t3; t4], when you want to run a tactic
[t1] that will generate a set of subgoals where each is solvable by at least
one of [t2], [t3], and [t4].
At the same time, it's often helpful to start proving the goal without using
the semicolon notation in some places, so that you can see interactively what
form the subgoals take. After you figure out the pattern and a general way of
solving them with tactics, you can convert your mess of tactics into a nice
single line with semicolons.
*)
Theorem constant_fold_simplifies : forall (e : exp),
constant_folded (constant_fold e).
Admitted.
(** Prove that your function is meaning-preserving. *)
Theorem constant_fold_sound : forall (ctx : var -> nat) (e : exp),
eval (constant_fold e) ctx = eval e ctx.
Admitted.
(** ** Arithmetic identities *)
(** This section is optional. *)
(** We would also like to simplify any additions of 0 and multiplications by 1.
This function formalizes what it means for an expression not to contain any
such pieces: *)
Fixpoint no_identities (e : exp) : Prop :=
match e with
| Const _ => True
| Var _ => True
| Plus (Const 0) _ => False
| Plus _ (Const 0) => False
| Plus e1 e2 => no_identities e1 /\ no_identities e2
| Times (Const 1) _ => False
| Times _ (Const 1) => False
| Times e1 e2 => no_identities e1 /\ no_identities e2
end.
(** Define a function for applying the identity simplification rules. *)
Definition simplify_identities : exp -> exp.
Admitted.
(** Prove that your function doesn't miss any opportunities. *)
Theorem simplify_identities_simplifies : forall (e : exp),
no_identities (simplify_identities e).
Admitted.
(** Prove that your function is meaning-preserving. *)
Theorem simplify_identities_sound : forall (ctx : var -> nat) (e : exp),
eval (simplify_identities e) ctx = eval e ctx.
Admitted.
(** OK, you're done if there are no more occurrences of "[Admitted]" in your
solution file and you haven't changed the specifications!
*)