Week 1: Basic Program Syntax
- Author
Adam Chlipala, with modifications by CS-428 staff.
- License
No redistribution allowed (usage by permission in CS-428).
Simple syntax
As a first example, let's look at the syntax of simple arithmetic expressions. We use the Coq feature of modules, which let us group related definitions together. A key benefit is that names can be reused across modules, which is helpful to define several variants of a suite of functionality, within a single source file.
Module ArithWithConstants.
The following definition closely mirrors a standard BNF grammar for expressions. It defines abstract syntax trees of arithmetic expressions.
Inductive arith :=
| Const (n : nat)
| Plus (e1 e2 : arith)
| Times (e1 e2 : arith).
Here are a few examples of specific expressions.
Example ex1 := Const 42. Example ex2 := Plus (Const 1) (Times (Const 2) (Const 3)).
What's the value of an expression?
Unlike in many programming languages, in Coq,
recursive functions must be marked as recursive explicitly.
That marking comes with the Fixpoint
command, as opposed to Definition
.
Note also that Coq checks termination of each recursive definition.
Intuitively, recursive calls must be on subterms of the original argument.
Fixpoint eval (e: arith) : nat :=
match e with
| Const n => n
| Plus e1 e2 => eval e1 + eval e2
| Times e1 e2 => eval e1 * eval e2
end.
We can run this program (evaluate the corresponding term) using Compute:
How many nodes appear in the tree for an expression?
Fixpoint size (e : arith) : nat := match e with | Const _ => 1 | Plus e1 e2 => 1 + size e1 + size e2 | Times e1 e2 => 1 + size e1 + size e2 end.
What's the longest path from the root of a syntax tree to a leaf?
Fixpoint depth (e : arith) : nat := match e with | Const _ => 1 | Plus e1 e2 => 1 + max (depth e1) (depth e2) | Times e1 e2 => 1 + max (depth e1) (depth e2) end.
Proofs by induction
Our first proof! Size is an upper bound on depth:
forall e : arith, depth e <= size eforall e : arith, depth e <= size e
Within a proof, we apply commands called tactics. Here's our first one. Keep in mind that the best way to understand what's going on is to run the proof script for yourself, inspecting intermediate states!
n: natdepth (Const n) <= size (Const n)e1, e2: arith
IHe1: depth e1 <= size e1
IHe2: depth e2 <= size e2depth (Plus e1 e2) <= size (Plus e1 e2)e1, e2: arith
IHe1: depth e1 <= size e1
IHe2: depth e2 <= size e2depth (Times e1 e2) <= size (Times e1 e2)
induction x
Structure the proof by induction on the structure of
x
, wherex
is a variable in the theorem statement. You will get one generated subgoal per constructor in the inductive definition ofx
. (Indeed, it is required thatx
's type was introduced withInductive
.)
Bullets (-) are used to structure the proof: each bullet corresponds to one goal.
n: natdepth (Const n) <= size (Const n)n: nat1 <= 1
simpl
Simplify throughout the goal, applying the definitions of recursive functions directly. That is, when a subterm matches one of the
match
cases in a definingFixpoint
, replace with the body of that case, then repeat.
lia.
lia
A complete decision procedure for linear arithmetic. Relevant formulas are essentially those built up from variables and constant natural numbers and integers using only addition, with congruence and incongruence comparisons on top. (Multiplication by constants is supported, as a shorthand for repeated addition.)
e1, e2: arith
IHe1: depth e1 <= size e1
IHe2: depth e2 <= size e2depth (Plus e1 e2) <= size (Plus e1 e2)lia.e1, e2: arith
IHe1: depth e1 <= size e1
IHe2: depth e2 <= size e2S (max (depth e1) (depth e2)) <= S (size e1 + size e2)e1, e2: arith
IHe1: depth e1 <= size e1
IHe2: depth e2 <= size e2depth (Times e1 e2) <= size (Times e1 e2)lia. Qed.e1, e2: arith
IHe1: depth e1 <= size e1
IHe2: depth e2 <= size e2S (max (depth e1) (depth e2)) <= S (size e1 + size e2)
We are applying the same operations to all goals — wouldn't it be better to say just that?
forall e : arith, depth e <= size eforall e : arith, depth e <= size en: natdepth (Const n) <= size (Const n)e1, e2: arith
IHe1: depth e1 <= size e1
IHe2: depth e2 <= size e2depth (Plus e1 e2) <= size (Plus e1 e2)e1, e2: arith
IHe1: depth e1 <= size e1
IHe2: depth e2 <= size e2depth (Times e1 e2) <= size (Times e1 e2)all: lia. Qed.n: nat1 <= 1e1, e2: arith
IHe1: depth e1 <= size e1
IHe2: depth e2 <= size e2S (max (depth e1) (depth e2)) <= S (size e1 + size e2)e1, e2: arith
IHe1: depth e1 <= size e1
IHe2: depth e2 <= size e2S (max (depth e1) (depth e2)) <= S (size e1 + size e2)
Or even better, look at that!
forall e : arith, depth e <= size einduction e; simpl; lia.forall e : arith, depth e <= size e
Chaining tactics with semicolon, as in t1; t2
,
asks to run t1
on the goal, then run t2
on every
generated subgoal. This is an essential ingredient for automation.
Qed.
A silly recursive function: swap the operand orders of all binary operators.
Fixpoint commuter (e : arith) : arith := match e with | Const _ => e | Plus e1 e2 => Plus (commuter e2) (commuter e1) | Times e1 e2 => Times (commuter e2) (commuter e1) end.
commuter
has all the appropriate interactions with other functions (and itself).
forall e : arith, eval (commuter e) = eval einduction e; simpl; lia. Qed.forall e : arith, eval (commuter e) = eval eforall e : arith, size (commuter e) = size einduction e; simpl; lia. Qed.forall e : arith, size (commuter e) = size eforall e : arith, depth (commuter e) = depth einduction e; simpl; lia. Qed.forall e : arith, depth (commuter e) = depth e
This last theorem is a bit different, since we prove equalities on arith terms, rather than natural numbers.
forall e : arith, commuter (commuter e) = eforall e : arith, commuter (commuter e) = en: natConst n = Const ne1, e2: arith
IHe1: commuter (commuter e1) = e1
IHe2: commuter (commuter e2) = e2Plus (commuter (commuter e1)) (commuter (commuter e2)) = Plus e1 e2e1, e2: arith
IHe1: commuter (commuter e1) = e1
IHe2: commuter (commuter e2) = e2Times (commuter (commuter e1)) (commuter (commuter e2)) = Times e1 e2
To conclude the proof, we find ourselves needing a new tactic, congruence
:
all: congruence.
congruence
A complete decision procedure for the theory of equality and uninterpreted functions. That is, the goal must follow from only reflexivity, symmetry, transitivity, and congruence of equality, including that functions really do behave as functions.
Qed.
Working with variables
Let's shake things up a bit by adding variables to expressions. Note that all of the automated proof scripts from before will keep working with no changes! That sort of "free" proof evolution is invaluable for theorems about real-world compilers, say.
Module ArithWithVariables.
For readability, we let var
refer to string
s, using the Notation
command,
and use it in the definition of the new Var
constructor:
Notation var := string. Inductive arith : Set := | Const (n : nat) | Var (x : var) (* <-- this is the new constructor! *) | Plus (e1 e2 : arith) | Times (e1 e2 : arith). Example ex1 := Const 42. Example ex2 := Plus (Const 1) (Times (Var "x") (Var "y")).
Adjusting eval
to the Var
case requires a new argument,
vn: val
, which maps variable names to values:
Notation val := (var -> nat). Fixpoint eval (vn: val) (e: arith) : nat := match e with | Const n => n | Var v => vn v | Plus e1 e2 => eval vn e1 + eval vn e2 | Times e1 e2 => eval vn e1 * eval vn e2 end.
The simplest valuation returns 0 for every input:
Example val0: val := fun _ => 0.
… but we can also pass a more complex function:
The functions size
, depth
, and commuter
are similarly easy to adjust,
this time with no additional parameters needed:
Fixpoint size (e : arith) : nat := match e with | Const _ => 1 | Var _ => 1 | Plus e1 e2 => 1 + size e1 + size e2 | Times e1 e2 => 1 + size e1 + size e2 end.Fixpoint depth (e : arith) : nat := match e with | Const _ => 1 | Var _ => 1 | Plus e1 e2 => 1 + max (depth e1) (depth e2) | Times e1 e2 => 1 + max (depth e1) (depth e2) end.Fixpoint commuter (e : arith) : arith := match e with | Const _ => e | Var _ => e | Plus e1 e2 => Plus (commuter e2) (commuter e1) | Times e1 e2 => Times (commuter e2) (commuter e1) end.
Due to the automation, the proofs are unchanged:
forall e : arith, depth e <= size einduction e; simpl; lia. Qed.forall e : arith, depth e <= size eforall (vn : val) (e : arith), eval vn (commuter e) = eval vn einduction e; simpl; lia. Qed.forall (vn : val) (e : arith), eval vn (commuter e) = eval vn eforall e : arith, size (commuter e) = size einduction e; simpl; lia. Qed.forall e : arith, size (commuter e) = size eforall e : arith, depth (commuter e) = depth einduction e; simpl; lia. Qed.forall e : arith, depth (commuter e) = depth eforall e : arith, commuter (commuter e) = einduction e; simpl; congruence. Qed.forall e : arith, commuter (commuter e) = e
Complex transformations
Variable substitution
Now that we have variables, we can consider new operations, like substituting an expression for a variable.
To define this operation,
we use an infix operator ==
for equality tests on strings.
It has a somewhat funny and very expressive type,
whose details we will try to gloss over.
Infix "==" := string_dec (at level 70).
Fixpoint substitute (inThis : arith) (replaceThis : var) (withThis : arith) : arith := match inThis with | Const _ => inThis | Var x => if x == replaceThis then withThis else inThis | Plus e1 e2 => Plus (substitute e1 replaceThis withThis) (substitute e2 replaceThis withThis) | Times e1 e2 => Times (substitute e1 replaceThis withThis) (substitute e2 replaceThis withThis) end.
An intuitive property about how much substitute
might increase depth:
forall (replaceThis : var) (withThis inThis : arith), depth (substitute inThis replaceThis withThis) <= depth inThis + depth withThisforall (replaceThis : var) (withThis inThis : arith), depth (substitute inThis replaceThis withThis) <= depth inThis + depth withThisreplaceThis: var
withThis: arith
n: nat1 <= S (depth withThis)replaceThis: var
withThis: arith
x: vardepth (if x == replaceThis then withThis else Var x) <= S (depth withThis)replaceThis: var
withThis, inThis1, inThis2: arith
IHinThis1: depth (substitute inThis1 replaceThis withThis) <= depth inThis1 + depth withThis
IHinThis2: depth (substitute inThis2 replaceThis withThis) <= depth inThis2 + depth withThisS (max (depth (substitute inThis1 replaceThis withThis)) (depth (substitute inThis2 replaceThis withThis))) <= S (max (depth inThis1) (depth inThis2) + depth withThis)replaceThis: var
withThis, inThis1, inThis2: arith
IHinThis1: depth (substitute inThis1 replaceThis withThis) <= depth inThis1 + depth withThis
IHinThis2: depth (substitute inThis2 replaceThis withThis) <= depth inThis2 + depth withThisS (max (depth (substitute inThis1 replaceThis withThis)) (depth (substitute inThis2 replaceThis withThis))) <= S (max (depth inThis1) (depth inThis2) + depth withThis)lia.replaceThis: var
withThis: arith
n: nat1 <= S (depth withThis)replaceThis: var
withThis: arith
x: vardepth (if x == replaceThis then withThis else Var x) <= S (depth withThis)replaceThis: var
withThis: arith
x: var
e: x = replaceThisdepth withThis <= S (depth withThis)replaceThis: var
withThis: arith
x: var
n: x <> replaceThis1 <= S (depth withThis)
destruct e
Break the proof into one case for each constructor that might have been used to build the value of expression
e
. In the special case wheree
essentially has a Boolean type, we consider whethere
is true or false.
lia.replaceThis: var
withThis: arith
x: var
e: x = replaceThisdepth withThis <= S (depth withThis)lia.replaceThis: var
withThis: arith
x: var
n: x <> replaceThis1 <= S (depth withThis)lia.replaceThis: var
withThis, inThis1, inThis2: arith
IHinThis1: depth (substitute inThis1 replaceThis withThis) <= depth inThis1 + depth withThis
IHinThis2: depth (substitute inThis2 replaceThis withThis) <= depth inThis2 + depth withThisS (max (depth (substitute inThis1 replaceThis withThis)) (depth (substitute inThis2 replaceThis withThis))) <= S (max (depth inThis1) (depth inThis2) + depth withThis)lia. Qed.replaceThis: var
withThis, inThis1, inThis2: arith
IHinThis1: depth (substitute inThis1 replaceThis withThis) <= depth inThis1 + depth withThis
IHinThis2: depth (substitute inThis2 replaceThis withThis) <= depth inThis2 + depth withThisS (max (depth (substitute inThis1 replaceThis withThis)) (depth (substitute inThis2 replaceThis withThis))) <= S (max (depth inThis1) (depth inThis2) + depth withThis)
Let's get fancier about automation, using match goal
to pattern-match the goal
and decide what to do next!
The |-
syntax separates hypotheses and conclusion in a goal.
The context
syntax is for matching against any subterm of a term.
The construct try
is also useful, for attempting a tactic and rolling back
the effect if any error is encountered.
forall (replaceThis : var) (withThis inThis : arith), depth (substitute inThis replaceThis withThis) <= depth inThis + depth withThisinduction inThis; simpl; try match goal with | [ |- context[if ?a == ?b then _ else _] ] => destruct (a == b); simpl end; lia. Qed.forall (replaceThis : var) (withThis inThis : arith), depth (substitute inThis replaceThis withThis) <= depth inThis + depth withThis
A silly self-substitution has no effect.
forall (replaceThis : var) (inThis : arith), substitute inThis replaceThis (Var replaceThis) = inThisinduction inThis; simpl; try match goal with | [ |- context[if ?a == ?b then _ else _] ] => destruct (a == b); simpl end; congruence. Qed.forall (replaceThis : var) (inThis : arith), substitute inThis replaceThis (Var replaceThis) = inThis
We can do substitution and commuting in either order.
forall (replaceThis : var) (withThis inThis : arith), commuter (substitute inThis replaceThis withThis) = substitute (commuter inThis) replaceThis (commuter withThis)induction inThis; simpl; try match goal with | [ |- context[if ?a == ?b then _ else _] ] => destruct (a == b); simpl end; congruence. Qed.forall (replaceThis : var) (withThis inThis : arith), commuter (substitute inThis replaceThis withThis) = substitute (commuter inThis) replaceThis (commuter withThis)
Constant folding
Constant folding is one of the classic compiler optimizations. We repeatedly find opportunities to replace fancier expressions with known constant values.
Fixpoint constantFold (e : arith) : arith :=
match e with
| Const _ => e
| Var _ => e
| Plus e1 e2 =>
let e1' := constantFold e1 in
let e2' := constantFold e2 in
match e1', e2' with
| Const n1, Const n2 => Const (n1 + n2)
| Const 0, _ => e2'
| _, Const 0 => e1'
| _, _ => Plus e1' e2'
end
| Times e1 e2 =>
let e1' := constantFold e1 in
let e2' := constantFold e2 in
match e1', e2' with
| Const n1, Const n2 => Const (n1 * n2)
| Const 1, _ => e2'
| _, Const 1 => e1'
| Const 0, _ => Const 0
| _, Const 0 => Const 0
| _, _ => Times e1' e2'
end
end.
This is supposed to be an optimization, so it had better not increase the size of an expression!
As before, automation makes short work of the proof.
A new scripting construct is match
patterns with dummy bodies.
Such a pattern matches any match
in a goal, over any type!
forall e : arith, size (constantFold e) <= size einduction e; simpl; repeat match goal with | [ |- context[match ?E with _ => _ end] ] => destruct E; simpl in * end; lia. Qed.forall e : arith, size (constantFold e) <= size e
The same principle applies for eval
:
forall (vn : val) (e : arith), eval vn (constantFold e) = eval vn einduction e; simpl; repeat match goal with | [ |- context[match ?E with _ => _ end] ] => destruct E; simpl in * end; lia. Qed.forall (vn : val) (e : arith), eval vn (constantFold e) = eval vn e
And it's business as usual for commuter
, with just two simple added cases:
forall e : arith, commuter (constantFold e) = constantFold (commuter e)induction e; simpl; repeat match goal with | [ |- context[match ?E with _ => _ end] ] => destruct E; simpl in * | [ H : ?f _ = ?f _ |- _ ] => injection H as H; subst | [ |- ?f _ = ?f _ ] => f_equal end; try (lia || congruence).forall e : arith, commuter (constantFold e) = constantFold (commuter e)
injection H as H
Replace a hypothesis
H
of the formf x = f y
withx = y
, whenf
is a constructor of an inductive type.f_equal
Replace a goal of the form
f x = f y
withx = y
, for any functionf
.Qed.
Pushing constants into multiplications
To define our last transformation, we first write a roundabout way of
testing whether an expression is a constant.
This detour happens to be useful to avoid overhead in concert with
pattern matching, since Coq internally elaborates wildcard _
patterns
into separate destruct for all constructors not considered beforehand.
That expansion can create serious code blow-ups, leading to serious
proof blow-ups!
Definition isConst (e : arith) : option nat :=
match e with
| Const n => Some n
| _ => None
end.
Our next target is a function that finds multiplications by constants
and pushes the multiplications to the leaves of syntax trees,
ideally finding constants, which can be replaced by larger constants,
not affecting the meanings of expressions.
This helper function takes a coefficient multiplyBy
that should be
applied to an expression.
Fixpoint pushMultiplicationInside'
(multiplyBy : nat) (e : arith) : arith :=
match e with
| Const n => Const (multiplyBy * n)
| Var _ => Times (Const multiplyBy) e
| Plus e1 e2 => Plus (pushMultiplicationInside' multiplyBy e1)
(pushMultiplicationInside' multiplyBy e2)
| Times e1 e2 =>
match isConst e1 with
| Some k => pushMultiplicationInside' (k * multiplyBy) e2
| None => Times (pushMultiplicationInside' multiplyBy e1) e2
end
end.
The overall transformation just fixes the initial coefficient as 1
.
Definition pushMultiplicationInside (e : arith) : arith :=
pushMultiplicationInside' 1 e.
A fun fact about pushing multiplication inside: the coefficient has no effect on depth! Let's start by showing any coefficient is equivalent to coefficient 0.
forall (e : arith) (multiplyBy : nat), depth (pushMultiplicationInside' multiplyBy e) = depth (pushMultiplicationInside' 0 e)forall (e : arith) (multiplyBy : nat), depth (pushMultiplicationInside' multiplyBy e) = depth (pushMultiplicationInside' 0 e)n, multiplyBy: nat1 = 1x: var
multiplyBy: nat2 = 2e1, e2: arith
IHe1: forall multiplyBy : nat, depth (pushMultiplicationInside' multiplyBy e1) = depth (pushMultiplicationInside' 0 e1)
IHe2: forall multiplyBy : nat, depth (pushMultiplicationInside' multiplyBy e2) = depth (pushMultiplicationInside' 0 e2)
multiplyBy: natS (max (depth (pushMultiplicationInside' multiplyBy e1)) (depth (pushMultiplicationInside' multiplyBy e2))) = S (max (depth (pushMultiplicationInside' 0 e1)) (depth (pushMultiplicationInside' 0 e2)))e1, e2: arith
IHe1: forall multiplyBy : nat, depth (pushMultiplicationInside' multiplyBy e1) = depth (pushMultiplicationInside' 0 e1)
IHe2: forall multiplyBy : nat, depth (pushMultiplicationInside' multiplyBy e2) = depth (pushMultiplicationInside' 0 e2)
multiplyBy: natdepth match isConst e1 with | Some k => pushMultiplicationInside' (k * multiplyBy) e2 | None => Times (pushMultiplicationInside' multiplyBy e1) e2 end = depth match isConst e1 with | Some k => pushMultiplicationInside' (k * 0) e2 | None => Times (pushMultiplicationInside' 0 e1) e2 endlia.n, multiplyBy: nat1 = 1lia.x: var
multiplyBy: nat2 = 2e1, e2: arith
IHe1: forall multiplyBy : nat, depth (pushMultiplicationInside' multiplyBy e1) = depth (pushMultiplicationInside' 0 e1)
IHe2: forall multiplyBy : nat, depth (pushMultiplicationInside' multiplyBy e2) = depth (pushMultiplicationInside' 0 e2)
multiplyBy: natS (max (depth (pushMultiplicationInside' multiplyBy e1)) (depth (pushMultiplicationInside' multiplyBy e2))) = S (max (depth (pushMultiplicationInside' 0 e1)) (depth (pushMultiplicationInside' 0 e2)))e1, e2: arith
IHe1: forall multiplyBy : nat, depth (pushMultiplicationInside' multiplyBy e1) = depth (pushMultiplicationInside' 0 e1)
IHe2: forall multiplyBy : nat, depth (pushMultiplicationInside' multiplyBy e2) = depth (pushMultiplicationInside' 0 e2)
multiplyBy: natS (max (depth (pushMultiplicationInside' 0 e1)) (depth (pushMultiplicationInside' multiplyBy e2))) = S (max (depth (pushMultiplicationInside' 0 e1)) (depth (pushMultiplicationInside' 0 e2)))
rewrite H
Where
H
is a hypothesis or previously proved theorem, establishingforall x1 .. xN, e1 = e2
, find a subterm of the goal that equalse1
, given the right choices ofxi
values, and replace that subterm withe2
.
lia.e1, e2: arith
IHe1: forall multiplyBy : nat, depth (pushMultiplicationInside' multiplyBy e1) = depth (pushMultiplicationInside' 0 e1)
IHe2: forall multiplyBy : nat, depth (pushMultiplicationInside' multiplyBy e2) = depth (pushMultiplicationInside' 0 e2)
multiplyBy: natS (max (depth (pushMultiplicationInside' 0 e1)) (depth (pushMultiplicationInside' 0 e2))) = S (max (depth (pushMultiplicationInside' 0 e1)) (depth (pushMultiplicationInside' 0 e2)))e1, e2: arith
IHe1: forall multiplyBy : nat, depth (pushMultiplicationInside' multiplyBy e1) = depth (pushMultiplicationInside' 0 e1)
IHe2: forall multiplyBy : nat, depth (pushMultiplicationInside' multiplyBy e2) = depth (pushMultiplicationInside' 0 e2)
multiplyBy: natdepth match isConst e1 with | Some k => pushMultiplicationInside' (k * multiplyBy) e2 | None => Times (pushMultiplicationInside' multiplyBy e1) e2 end = depth match isConst e1 with | Some k => pushMultiplicationInside' (k * 0) e2 | None => Times (pushMultiplicationInside' 0 e1) e2 ende1, e2: arith
IHe1: forall multiplyBy : nat, depth (pushMultiplicationInside' multiplyBy e1) = depth (pushMultiplicationInside' 0 e1)
IHe2: forall multiplyBy : nat, depth (pushMultiplicationInside' multiplyBy e2) = depth (pushMultiplicationInside' 0 e2)
multiplyBy, n: natdepth (pushMultiplicationInside' (n * multiplyBy) e2) = depth (pushMultiplicationInside' (n * 0) e2)e1, e2: arith
IHe1: forall multiplyBy : nat, depth (pushMultiplicationInside' multiplyBy e1) = depth (pushMultiplicationInside' 0 e1)
IHe2: forall multiplyBy : nat, depth (pushMultiplicationInside' multiplyBy e2) = depth (pushMultiplicationInside' 0 e2)
multiplyBy: natS (max (depth (pushMultiplicationInside' multiplyBy e1)) (depth e2)) = S (max (depth (pushMultiplicationInside' 0 e1)) (depth e2))e1, e2: arith
IHe1: forall multiplyBy : nat, depth (pushMultiplicationInside' multiplyBy e1) = depth (pushMultiplicationInside' 0 e1)
IHe2: forall multiplyBy : nat, depth (pushMultiplicationInside' multiplyBy e2) = depth (pushMultiplicationInside' 0 e2)
multiplyBy, n: natdepth (pushMultiplicationInside' (n * multiplyBy) e2) = depth (pushMultiplicationInside' (n * 0) e2)e1, e2: arith
IHe1: forall multiplyBy : nat, depth (pushMultiplicationInside' multiplyBy e1) = depth (pushMultiplicationInside' 0 e1)
IHe2: forall multiplyBy : nat, depth (pushMultiplicationInside' multiplyBy e2) = depth (pushMultiplicationInside' 0 e2)
multiplyBy, n: natdepth (pushMultiplicationInside' 0 e2) = depth (pushMultiplicationInside' (n * 0) e2)
Neither lia
nor congruence
is powerful enough on its own here,
so we use a separate lemma to simplify n * 0
:
e1, e2: arith
IHe1: forall multiplyBy : nat, depth (pushMultiplicationInside' multiplyBy e1) = depth (pushMultiplicationInside' 0 e1)
IHe2: forall multiplyBy : nat, depth (pushMultiplicationInside' multiplyBy e2) = depth (pushMultiplicationInside' 0 e2)
multiplyBy, n: natdepth (pushMultiplicationInside' 0 e2) = depth (pushMultiplicationInside' (n * 0) e2)lia.e1, e2: arith
IHe1: forall multiplyBy : nat, depth (pushMultiplicationInside' multiplyBy e1) = depth (pushMultiplicationInside' 0 e1)
IHe2: forall multiplyBy : nat, depth (pushMultiplicationInside' multiplyBy e2) = depth (pushMultiplicationInside' 0 e2)
multiplyBy, n: natdepth (pushMultiplicationInside' 0 e2) = depth (pushMultiplicationInside' 0 e2)e1, e2: arith
IHe1: forall multiplyBy : nat, depth (pushMultiplicationInside' multiplyBy e1) = depth (pushMultiplicationInside' 0 e1)
IHe2: forall multiplyBy : nat, depth (pushMultiplicationInside' multiplyBy e2) = depth (pushMultiplicationInside' 0 e2)
multiplyBy: natS (max (depth (pushMultiplicationInside' multiplyBy e1)) (depth e2)) = S (max (depth (pushMultiplicationInside' 0 e1)) (depth e2))lia. Qed.e1, e2: arith
IHe1: forall multiplyBy : nat, depth (pushMultiplicationInside' multiplyBy e1) = depth (pushMultiplicationInside' 0 e1)
IHe2: forall multiplyBy : nat, depth (pushMultiplicationInside' multiplyBy e2) = depth (pushMultiplicationInside' 0 e2)
multiplyBy: natS (max (depth (pushMultiplicationInside' 0 e1)) (depth e2)) = S (max (depth (pushMultiplicationInside' 0 e1)) (depth e2))
It can be remarkably hard to get Coq's automation to be dumb enough to help us demonstrate all of the primitive tactics. ;-) In particular, we can redo the proof in an automated way, without the explicit rewrites.
forall (e : arith) (multiplyBy : nat), depth (pushMultiplicationInside' multiplyBy e) = depth (pushMultiplicationInside' 0 e)induction e; simpl; try match goal with | [ |- context[match ?E with _ => _ end] ] => destruct E; simpl end; congruence. Qed.forall (e : arith) (multiplyBy : nat), depth (pushMultiplicationInside' multiplyBy e) = depth (pushMultiplicationInside' 0 e)
Now the general corollary about irrelevance of coefficients for depth.
forall (e : arith) (multiplyBy1 multiplyBy2 : nat), depth (pushMultiplicationInside' multiplyBy1 e) = depth (pushMultiplicationInside' multiplyBy2 e)forall (e : arith) (multiplyBy1 multiplyBy2 : nat), depth (pushMultiplicationInside' multiplyBy1 e) = depth (pushMultiplicationInside' multiplyBy2 e)e: arith
multiplyBy1, multiplyBy2: natdepth (pushMultiplicationInside' multiplyBy1 e) = depth (pushMultiplicationInside' multiplyBy2 e)e: arith
multiplyBy1, multiplyBy2: natdepth (pushMultiplicationInside' multiplyBy1 e) = depth (pushMultiplicationInside' 0 e)e: arith
multiplyBy1, multiplyBy2: natdepth (pushMultiplicationInside' 0 e) = depth (pushMultiplicationInside' multiplyBy2 e)apply depth_pushMultiplicationInside'_irrelevance0.e: arith
multiplyBy1, multiplyBy2: natdepth (pushMultiplicationInside' multiplyBy1 e) = depth (pushMultiplicationInside' 0 e)
apply H
For
H
a hypothesis or previously proved theorem, establishing some fact that matches the structure of the current conclusion, switch to provingH
's own hypotheses. This is backwards reasoning via a known fact.
e: arith
multiplyBy1, multiplyBy2: natdepth (pushMultiplicationInside' 0 e) = depth (pushMultiplicationInside' multiplyBy2 e)e: arith
multiplyBy1, multiplyBy2: natdepth (pushMultiplicationInside' multiplyBy2 e) = depth (pushMultiplicationInside' 0 e)
symmetry
When proving
X ℛ Y
, switch to provingY ℛ X
(hereℛ
is=
).
apply depth_pushMultiplicationInside'_irrelevance0. Qed.
Let's prove that pushing-inside has only a small effect on depth, considering for now only coefficient 0.
forall e : arith, depth (pushMultiplicationInside' 0 e) <= S (depth e)forall e : arith, depth (pushMultiplicationInside' 0 e) <= S (depth e)n: nat1 <= 2x: var2 <= 2e1, e2: arith
IHe1: depth (pushMultiplicationInside' 0 e1) <= S (depth e1)
IHe2: depth (pushMultiplicationInside' 0 e2) <= S (depth e2)S (max (depth (pushMultiplicationInside' 0 e1)) (depth (pushMultiplicationInside' 0 e2))) <= S (S (max (depth e1) (depth e2)))e1, e2: arith
IHe1: depth (pushMultiplicationInside' 0 e1) <= S (depth e1)
IHe2: depth (pushMultiplicationInside' 0 e2) <= S (depth e2)depth match isConst e1 with | Some k => pushMultiplicationInside' (k * 0) e2 | None => Times (pushMultiplicationInside' 0 e1) e2 end <= S (S (max (depth e1) (depth e2)))lia.n: nat1 <= 2lia.x: var2 <= 2lia.e1, e2: arith
IHe1: depth (pushMultiplicationInside' 0 e1) <= S (depth e1)
IHe2: depth (pushMultiplicationInside' 0 e2) <= S (depth e2)S (max (depth (pushMultiplicationInside' 0 e1)) (depth (pushMultiplicationInside' 0 e2))) <= S (S (max (depth e1) (depth e2)))e1, e2: arith
IHe1: depth (pushMultiplicationInside' 0 e1) <= S (depth e1)
IHe2: depth (pushMultiplicationInside' 0 e2) <= S (depth e2)depth match isConst e1 with | Some k => pushMultiplicationInside' (k * 0) e2 | None => Times (pushMultiplicationInside' 0 e1) e2 end <= S (S (max (depth e1) (depth e2)))e1, e2: arith
IHe1: depth (pushMultiplicationInside' 0 e1) <= S (depth e1)
IHe2: depth (pushMultiplicationInside' 0 e2) <= S (depth e2)
n: natdepth (pushMultiplicationInside' (n * 0) e2) <= S (S (max (depth e1) (depth e2)))e1, e2: arith
IHe1: depth (pushMultiplicationInside' 0 e1) <= S (depth e1)
IHe2: depth (pushMultiplicationInside' 0 e2) <= S (depth e2)S (max (depth (pushMultiplicationInside' 0 e1)) (depth e2)) <= S (S (max (depth e1) (depth e2)))e1, e2: arith
IHe1: depth (pushMultiplicationInside' 0 e1) <= S (depth e1)
IHe2: depth (pushMultiplicationInside' 0 e2) <= S (depth e2)
n: natdepth (pushMultiplicationInside' (n * 0) e2) <= S (S (max (depth e1) (depth e2)))lia.e1, e2: arith
IHe1: depth (pushMultiplicationInside' 0 e1) <= S (depth e1)
IHe2: depth (pushMultiplicationInside' 0 e2) <= S (depth e2)
n: natdepth (pushMultiplicationInside' 0 e2) <= S (S (max (depth e1) (depth e2)))lia. Qed. #[local] Hint Rewrite mul_0_r : arith.e1, e2: arith
IHe1: depth (pushMultiplicationInside' 0 e1) <= S (depth e1)
IHe2: depth (pushMultiplicationInside' 0 e2) <= S (depth e2)S (max (depth (pushMultiplicationInside' 0 e1)) (depth e2)) <= S (S (max (depth e1) (depth e2)))
Registering rewrite hints lets us use autorewrite
to apply them for us
automatically!
forall e : arith, depth (pushMultiplicationInside' 0 e) <= S (depth e)induction e; intros; simpl in *; try match goal with | [ |- context[match ?E with _ => _ end] ] => destruct E; simpl; autorewrite with arith end; lia. Qed.forall e : arith, depth (pushMultiplicationInside' 0 e) <= S (depth e)forall e : arith, depth (pushMultiplicationInside e) <= S (depth e)forall e : arith, depth (pushMultiplicationInside e) <= S (depth e)e: arithdepth (pushMultiplicationInside e) <= S (depth e)e: arithdepth (pushMultiplicationInside' 1 e) <= S (depth e)
unfold X
Replace
X
by its definition.
apply depth_pushMultiplicationInside'. Qed. End ArithWithVariables.e: arithdepth (pushMultiplicationInside' 0 e) <= S (depth e)