ImpSimple Imperative Programs
Z ::= X;;
Y ::= 1;;
WHILE ! (Z = 0) DO
Y ::= Y * Z;;
Z ::= Z - 1
END
Y ::= 1;;
WHILE ! (Z = 0) DO
Y ::= Y * Z;;
Z ::= Z - 1
END
Arithmetic and Boolean Expressions
Abstract syntax trees for arithmetic and boolean expressions:
Inductive aexp : Type :=
| ANum (n : nat)
| APlus (a1 a2 : aexp)
| AMinus (a1 a2 : aexp)
| AMult (a1 a2 : aexp).
Inductive bexp : Type :=
| BTrue
| BFalse
| BEq (a1 a2 : aexp)
| BLe (a1 a2 : aexp)
| BNot (b : bexp)
| BAnd (b1 b2 : bexp).
| ANum (n : nat)
| APlus (a1 a2 : aexp)
| AMinus (a1 a2 : aexp)
| AMult (a1 a2 : aexp).
Inductive bexp : Type :=
| BTrue
| BFalse
| BEq (a1 a2 : aexp)
| BLe (a1 a2 : aexp)
| BNot (b : bexp)
| BAnd (b1 b2 : bexp).
For comparison, here's a conventional BNF (Backus-Naur Form) grammar defining the same abstract syntax:
a ::= nat
| a + a
| a - a
| a * a
b ::= true
| false
| a = a
| a ≤ a
| not b
| b and b
| a + a
| a - a
| a * a
b ::= true
| false
| a = a
| a ≤ a
| not b
| b and b
Fixpoint aeval (a : aexp) : nat :=
match a with
| ANum n ⇒ n
| APlus a1 a2 ⇒ (aeval a1) + (aeval a2)
| AMinus a1 a2 ⇒ (aeval a1) - (aeval a2)
| AMult a1 a2 ⇒ (aeval a1) * (aeval a2)
end.
Example test_aeval1:
aeval (APlus (ANum 2) (ANum 2)) = 4.
match a with
| ANum n ⇒ n
| APlus a1 a2 ⇒ (aeval a1) + (aeval a2)
| AMinus a1 a2 ⇒ (aeval a1) - (aeval a2)
| AMult a1 a2 ⇒ (aeval a1) * (aeval a2)
end.
Example test_aeval1:
aeval (APlus (ANum 2) (ANum 2)) = 4.
Proof. reflexivity. Qed.
Fixpoint beval (b : bexp) : bool :=
match b with
| BTrue ⇒ true
| BFalse ⇒ false
| BEq a1 a2 ⇒ (aeval a1) =? (aeval a2)
| BLe a1 a2 ⇒ (aeval a1) <=? (aeval a2)
| BNot b1 ⇒ negb (beval b1)
| BAnd b1 b2 ⇒ andb (beval b1) (beval b2)
end.
match b with
| BTrue ⇒ true
| BFalse ⇒ false
| BEq a1 a2 ⇒ (aeval a1) =? (aeval a2)
| BLe a1 a2 ⇒ (aeval a1) <=? (aeval a2)
| BNot b1 ⇒ negb (beval b1)
| BAnd b1 b2 ⇒ andb (beval b1) (beval b2)
end.
What does the following expression evaluate to?
(1) true
(2) false
(3) 0
(4) 3
(5) 6
aeval (APlus (ANum 3) (AMinus (ANum 4) (ANum 1)))
Fixpoint optimize_0plus (a:aexp) : aexp :=
match a with
| ANum n ⇒
ANum n
| APlus (ANum 0) e2 ⇒
optimize_0plus e2
| APlus e1 e2 ⇒
APlus (optimize_0plus e1) (optimize_0plus e2)
| AMinus e1 e2 ⇒
AMinus (optimize_0plus e1) (optimize_0plus e2)
| AMult e1 e2 ⇒
AMult (optimize_0plus e1) (optimize_0plus e2)
end.
Example test_optimize_0plus:
optimize_0plus (APlus (ANum 2)
(APlus (ANum 0)
(APlus (ANum 0) (ANum 1))))
= APlus (ANum 2) (ANum 1).
Proof. reflexivity. Qed.
Theorem optimize_0plus_sound: ∀ a,
aeval (optimize_0plus a) = aeval a.
Proof.
intros a. induction a.
- (* ANum *) reflexivity.
- (* APlus *) destruct a1 eqn:Ea1.
+ (* a1 = ANum n *) destruct n eqn:En.
* (* n = 0 *) simpl. apply IHa2.
* (* n <> 0 *) simpl. rewrite IHa2. reflexivity.
+ (* a1 = APlus a1_1 a1_2 *)
simpl. simpl in IHa1. rewrite IHa1.
rewrite IHa2. reflexivity.
+ (* a1 = AMinus a1_1 a1_2 *)
simpl. simpl in IHa1. rewrite IHa1.
rewrite IHa2. reflexivity.
+ (* a1 = AMult a1_1 a1_2 *)
simpl. simpl in IHa1. rewrite IHa1.
rewrite IHa2. reflexivity.
- (* AMinus *)
simpl. rewrite IHa1. rewrite IHa2. reflexivity.
- (* AMult *)
simpl. rewrite IHa1. rewrite IHa2. reflexivity. Qed.
Coq Automation
Tacticals
The try Tactical
Theorem silly1 : ∀ ae, aeval ae = aeval ae.
Proof. try reflexivity. (* this just does reflexivity *) Qed.
Theorem silly2 : ∀ (P : Prop), P → P.
Proof.
intros P HP.
try reflexivity. (* just reflexivity would have failed *)
apply HP. (* we can still finish the proof in some other way *)
Qed.
Proof. try reflexivity. (* this just does reflexivity *) Qed.
Theorem silly2 : ∀ (P : Prop), P → P.
Proof.
intros P HP.
try reflexivity. (* just reflexivity would have failed *)
apply HP. (* we can still finish the proof in some other way *)
Qed.
The ; Tactical (Simple Form)
Lemma foo : ∀ n, 0 <=? n = true.
Proof.
intros.
destruct n eqn:E.
(* Leaves two subgoals, which are discharged identically... *)
- (* n=0 *) simpl. reflexivity.
- (* n=Sn' *) simpl. reflexivity.
Qed.
Proof.
intros.
destruct n eqn:E.
(* Leaves two subgoals, which are discharged identically... *)
- (* n=0 *) simpl. reflexivity.
- (* n=Sn' *) simpl. reflexivity.
Qed.
Lemma foo' : ∀ n, 0 <=? n = true.
Proof.
intros.
(* destruct the current goal *)
destruct n;
(* then simpl each resulting subgoal *)
simpl;
(* and do reflexivity on each resulting subgoal *)
reflexivity.
Qed.
Proof.
intros.
(* destruct the current goal *)
destruct n;
(* then simpl each resulting subgoal *)
simpl;
(* and do reflexivity on each resulting subgoal *)
reflexivity.
Qed.
Using try and ; together, we can get rid of the repetition in the proof that was bothering us a little while ago.
Theorem optimize_0plus_sound': ∀ a,
aeval (optimize_0plus a) = aeval a.
Proof.
intros a.
induction a;
(* Most cases follow directly by the IH... *)
try (simpl; rewrite IHa1; rewrite IHa2; reflexivity).
(* ... but the remaining cases -- ANum and APlus --
are different: *)
- (* ANum *) reflexivity.
- (* APlus *)
destruct a1 eqn:Ea1;
(* Again, most cases follow directly by the IH: *)
try (simpl; simpl in IHa1; rewrite IHa1;
rewrite IHa2; reflexivity).
(* The interesting case, on which the try...
does nothing, is when e1 = ANum n. In this
case, we have to destruct n (to see whether
the optimization applies) and rewrite with the
induction hypothesis. *)
+ (* a1 = ANum n *) destruct n eqn:En;
simpl; rewrite IHa2; reflexivity. Qed.
aeval (optimize_0plus a) = aeval a.
Proof.
intros a.
induction a;
(* Most cases follow directly by the IH... *)
try (simpl; rewrite IHa1; rewrite IHa2; reflexivity).
(* ... but the remaining cases -- ANum and APlus --
are different: *)
- (* ANum *) reflexivity.
- (* APlus *)
destruct a1 eqn:Ea1;
(* Again, most cases follow directly by the IH: *)
try (simpl; simpl in IHa1; rewrite IHa1;
rewrite IHa2; reflexivity).
(* The interesting case, on which the try...
does nothing, is when e1 = ANum n. In this
case, we have to destruct n (to see whether
the optimization applies) and rewrite with the
induction hypothesis. *)
+ (* a1 = ANum n *) destruct n eqn:En;
simpl; rewrite IHa2; reflexivity. Qed.
Theorem optimize_0plus_sound'': ∀ a,
aeval (optimize_0plus a) = aeval a.
Proof.
intros a.
induction a;
(* Most cases follow directly by the IH *)
try (simpl; rewrite IHa1; rewrite IHa2; reflexivity);
(* ... or are immediate by definition *)
try reflexivity.
(* The interesting case is when a = APlus a1 a2. *)
- (* APlus *)
destruct a1; try (simpl; simpl in IHa1; rewrite IHa1;
rewrite IHa2; reflexivity).
+ (* a1 = ANum n *) destruct n;
simpl; rewrite IHa2; reflexivity. Qed.
aeval (optimize_0plus a) = aeval a.
Proof.
intros a.
induction a;
(* Most cases follow directly by the IH *)
try (simpl; rewrite IHa1; rewrite IHa2; reflexivity);
(* ... or are immediate by definition *)
try reflexivity.
(* The interesting case is when a = APlus a1 a2. *)
- (* APlus *)
destruct a1; try (simpl; simpl in IHa1; rewrite IHa1;
rewrite IHa2; reflexivity).
+ (* a1 = ANum n *) destruct n;
simpl; rewrite IHa2; reflexivity. Qed.
The repeat Tactical
Theorem In8 : In 8 [1;2;3;4;5;6;7;8;9;10].
Proof.
repeat (try (left; reflexivity); right).
Qed.
Proof.
repeat (try (left; reflexivity); right).
Qed.
Defining New Tactic Notations
- Tactic Notation: syntax extension for tactics (good for simple macros)
- Ltac: scripting language for tactics (good for more sophisticated proof engineering)
- OCaml tactic scripting API (only for wizards)
Tactic Notation "simpl_and_try" tactic(c) :=
simpl;
try c.
simpl;
try c.
The omega Tactic
- numeric constants, addition (+ and S), subtraction (-
and pred), and multiplication by constants (this is what
makes it Presburger arithmetic),
- equality (= and ≠) and ordering (≤), and
- the logical connectives ∧, ∨, ¬, and →,
Example silly_presburger_example : ∀ m n o p,
m + n ≤ n + o ∧ o + 3 = p + 3 →
m ≤ p.
Proof.
intros. omega.
Qed.
m + n ≤ n + o ∧ o + 3 = p + 3 →
m ≤ p.
Proof.
intros. omega.
Qed.
A Few More Handy Tactics
- clear H: Delete hypothesis H from the context.
- subst x: Find an assumption x = e or e = x in the
context, replace x with e throughout the context and
current goal, and clear the assumption.
- subst: Substitute away all assumptions of the form x = e
or e = x.
- rename... into...: Change the name of a hypothesis in the
proof context. For example, if the context includes a variable
named x, then rename x into y will change all occurrences
of x to y.
- assumption: Try to find a hypothesis H in the context that
exactly matches the goal; if one is found, behave like exact
H.
- contradiction: Try to find a hypothesis H in the current
context that is logically equivalent to False. If one is
found, solve the goal.
- constructor: Try to find a constructor c (from some Inductive definition in the current environment) that can be applied to solve the current goal. If one is found, behave like apply c.
Evaluation as a Relation
Inductive aevalR : aexp → nat → Prop :=
| E_ANum n :
aevalR (ANum n) n
| E_APlus (e1 e2: aexp) (n1 n2: nat) :
aevalR e1 n1 →
aevalR e2 n2 →
aevalR (APlus e1 e2) (n1 + n2)
| E_AMinus (e1 e2: aexp) (n1 n2: nat) :
aevalR e1 n1 →
aevalR e2 n2 →
aevalR (AMinus e1 e2) (n1 - n2)
| E_AMult (e1 e2: aexp) (n1 n2: nat) :
aevalR e1 n1 →
aevalR e2 n2 →
aevalR (AMult e1 e2) (n1 * n2).
| E_ANum n :
aevalR (ANum n) n
| E_APlus (e1 e2: aexp) (n1 n2: nat) :
aevalR e1 n1 →
aevalR e2 n2 →
aevalR (APlus e1 e2) (n1 + n2)
| E_AMinus (e1 e2: aexp) (n1 n2: nat) :
aevalR e1 n1 →
aevalR e2 n2 →
aevalR (AMinus e1 e2) (n1 - n2)
| E_AMult (e1 e2: aexp) (n1 n2: nat) :
aevalR e1 n1 →
aevalR e2 n2 →
aevalR (AMult e1 e2) (n1 * n2).
Notation "e '\\' n"
:= (aevalR e n)
(at level 50, left associativity)
: type_scope.
:= (aevalR e n)
(at level 50, left associativity)
: type_scope.
If we "reserve" the notation in advance, we can even use it
in the definition:
Reserved Notation "e '\\' n" (at level 50, left associativity).
Inductive aevalR : aexp → nat → Prop :=
| E_ANum n :
(ANum n) \\ n
| E_APlus e1 e2 n1 n2 :
(e1 \\ n1) → (e2 \\ n2) → (APlus e1 e2) \\ (n1 + n2)
| E_AMinus e1 e2 n1 n2 :
(e1 \\ n1) → (e2 \\ n2) → (AMinus e1 e2) \\ (n1 - n2)
| E_AMult e1 e2 n1 n2 :
(e1 \\ n1) → (e2 \\ n2) → (AMult e1 e2) \\ (n1 * n2)
where "e '\\' n" := (aevalR e n) : type_scope.
Inductive aevalR : aexp → nat → Prop :=
| E_ANum n :
(ANum n) \\ n
| E_APlus e1 e2 n1 n2 :
(e1 \\ n1) → (e2 \\ n2) → (APlus e1 e2) \\ (n1 + n2)
| E_AMinus e1 e2 n1 n2 :
(e1 \\ n1) → (e2 \\ n2) → (AMinus e1 e2) \\ (n1 - n2)
| E_AMult e1 e2 n1 n2 :
(e1 \\ n1) → (e2 \\ n2) → (AMult e1 e2) \\ (n1 * n2)
where "e '\\' n" := (aevalR e n) : type_scope.
Inference Rule Notation
| E_APlus : ∀ (e1 e2: aexp) (n1 n2: nat),
aevalR e1 n1 →
aevalR e2 n2 →
aevalR (APlus e1 e2) (n1 + n2)
...would be written like this as an inference rule:
aevalR e1 n1 →
aevalR e2 n2 →
aevalR (APlus e1 e2) (n1 + n2)
e1 \\ n1 | |
e2 \\ n2 | (E_APlus) |
APlus e1 e2 \\ n1+n2 |
There is nothing very deep going on here:
- rule name corresponds to a constructor name
- above the line are premises
- below the line is conclusion
- metavariables like e1 and n1 are implicitly universally quantified
- the whole collection of rules is implicitly wrapped in an Inductive (sometimes we write this slightly more explicitly, as "...closed under these rules...")
Computational vs. Relational Definitions
Adding division
Inductive aexp : Type :=
| ANum (n : nat)
| APlus (a1 a2 : aexp)
| AMinus (a1 a2 : aexp)
| AMult (a1 a2 : aexp)
| ADiv (a1 a2 : aexp). (* <--- new *)
| ANum (n : nat)
| APlus (a1 a2 : aexp)
| AMinus (a1 a2 : aexp)
| AMult (a1 a2 : aexp)
| ADiv (a1 a2 : aexp). (* <--- new *)
Extending the definition of aeval to handle this new operation
would not be straightforward (what should we return as the result
of ADiv (ANum 5) (ANum 0)?). But extending aevalR is
straightforward.
Adding division, relationally
Reserved Notation "e '\\' n"
(at level 50, left associativity).
Inductive aevalR : aexp → nat → Prop :=
| E_ANum : ∀ (n:nat),
(ANum n) \\ n
| E_APlus : ∀ (a1 a2: aexp) (n1 n2 : nat),
(a1 \\ n1) → (a2 \\ n2) → (APlus a1 a2) \\ (n1 + n2)
| E_AMinus : ∀ (a1 a2: aexp) (n1 n2 : nat),
(a1 \\ n1) → (a2 \\ n2) → (AMinus a1 a2) \\ (n1 - n2)
| E_AMult : ∀ (a1 a2: aexp) (n1 n2 : nat),
(a1 \\ n1) → (a2 \\ n2) → (AMult a1 a2) \\ (n1 * n2)
| E_ADiv : ∀ (a1 a2: aexp) (n1 n2 n3: nat),
(a1 \\ n1) → (a2 \\ n2) → (n2 > 0) →
(mult n2 n3 = n1) → (ADiv a1 a2) \\ n3
where "a '\\' n" := (aevalR a n) : type_scope.
Adding Nondeterminism
Reserved Notation "e '\\' n" (at level 50, left associativity).
Inductive aexp : Type :=
| AAny (* <--- NEW *)
| ANum (n : nat)
| APlus (a1 a2 : aexp)
| AMinus (a1 a2 : aexp)
| AMult (a1 a2 : aexp).
Inductive aexp : Type :=
| AAny (* <--- NEW *)
| ANum (n : nat)
| APlus (a1 a2 : aexp)
| AMinus (a1 a2 : aexp)
| AMult (a1 a2 : aexp).
Again, extending aeval would be tricky, since now evaluation is
not a deterministic function from expressions to numbers, but
extending aevalR is no problem...
Adding nondeterminism, relationally
Inductive aevalR : aexp → nat → Prop :=
| E_Any : ∀ (n:nat),
AAny \\ n (* <--- new *)
| E_ANum : ∀ (n:nat),
(ANum n) \\ n
| E_APlus : ∀ (a1 a2: aexp) (n1 n2 : nat),
(a1 \\ n1) → (a2 \\ n2) → (APlus a1 a2) \\ (n1 + n2)
| E_AMinus : ∀ (a1 a2: aexp) (n1 n2 : nat),
(a1 \\ n1) → (a2 \\ n2) → (AMinus a1 a2) \\ (n1 - n2)
| E_AMult : ∀ (a1 a2: aexp) (n1 n2 : nat),
(a1 \\ n1) → (a2 \\ n2) → (AMult a1 a2) \\ (n1 * n2)
where "a '\\' n" := (aevalR a n) : type_scope.
Tradeoffs
On the other hand, functional definitions can often be more convenient:
- Functions are by definition deterministic and defined on all arguments; for a relation we have to show these properties explicitly if we need them.
- With functions we can also take advantage of Coq's computation mechanism to simplify expressions during proofs.
Ultimately, the choice often comes down to either the specifics of a particular situation or simply a question of taste. Indeed, in large Coq developments it is common to see a definition given in both functional and relational styles, plus a lemma stating that the two coincide, allowing further proofs to switch from one point of view to the other at will.
Expressions With Variables
States
Definition state := total_map nat.
Syntax
Inductive aexp : Type :=
| ANum (n : nat)
| AId (x : string) (* <----- NEW *)
| APlus (a1 a2 : aexp)
| AMinus (a1 a2 : aexp)
| AMult (a1 a2 : aexp).
| ANum (n : nat)
| AId (x : string) (* <----- NEW *)
| APlus (a1 a2 : aexp)
| AMinus (a1 a2 : aexp)
| AMult (a1 a2 : aexp).
Defining a few variable names as notational shorthands will make
examples easier to read:
Definition W : string := "W".
Definition X : string := "X".
Definition Y : string := "Y".
Definition Z : string := "Z".
Definition X : string := "X".
Definition Y : string := "Y".
Definition Z : string := "Z".
Notations
To make Imp programs easier to read and write, we introduce some notations and implicit coercions.The notations below are declared in specific notation scopes, in order to avoid conflicts with other interpretations of the same symbols. Again, it is not necessary to understand the details.
Coercion AId : string >-> aexp.
Coercion ANum : nat >-> aexp.
Definition bool_to_bexp (b: bool) : bexp :=
if b then BTrue else BFalse.
Coercion bool_to_bexp : bool >-> bexp.
Bind Scope aexp_scope with aexp.
Infix "+" := APlus : aexp_scope.
Infix "-" := AMinus : aexp_scope.
Infix "*" := AMult : aexp_scope.
Bind Scope bexp_scope with bexp.
Infix "≤" := BLe : bexp_scope.
Infix "=" := BEq : bexp_scope.
Infix "&&" := BAnd : bexp_scope.
Notation "'!' b" := (BNot b) (at level 60) : bexp_scope.
Coercion ANum : nat >-> aexp.
Definition bool_to_bexp (b: bool) : bexp :=
if b then BTrue else BFalse.
Coercion bool_to_bexp : bool >-> bexp.
Bind Scope aexp_scope with aexp.
Infix "+" := APlus : aexp_scope.
Infix "-" := AMinus : aexp_scope.
Infix "*" := AMult : aexp_scope.
Bind Scope bexp_scope with bexp.
Infix "≤" := BLe : bexp_scope.
Infix "=" := BEq : bexp_scope.
Infix "&&" := BAnd : bexp_scope.
Notation "'!' b" := (BNot b) (at level 60) : bexp_scope.
We can now write 3 + (X * 2) instead of APlus 3 (AMult X 2),
and true && !(X ≤ 4) instead of BAnd true (BNot (BLe X 4)).
Fixpoint aeval (st : state) (a : aexp) : nat :=
match a with
| ANum n ⇒ n
| AId x ⇒ st x (* <----- NEW *)
| APlus a1 a2 ⇒ (aeval st a1) + (aeval st a2)
| AMinus a1 a2 ⇒ (aeval st a1) - (aeval st a2)
| AMult a1 a2 ⇒ (aeval st a1) * (aeval st a2)
end.
Fixpoint beval (st : state) (b : bexp) : bool :=
match b with
| BTrue ⇒ true
| BFalse ⇒ false
| BEq a1 a2 ⇒ (aeval st a1) =? (aeval st a2)
| BLe a1 a2 ⇒ (aeval st a1) <=? (aeval st a2)
| BNot b1 ⇒ negb (beval st b1)
| BAnd b1 b2 ⇒ andb (beval st b1) (beval st b2)
end.
match a with
| ANum n ⇒ n
| AId x ⇒ st x (* <----- NEW *)
| APlus a1 a2 ⇒ (aeval st a1) + (aeval st a2)
| AMinus a1 a2 ⇒ (aeval st a1) - (aeval st a2)
| AMult a1 a2 ⇒ (aeval st a1) * (aeval st a2)
end.
Fixpoint beval (st : state) (b : bexp) : bool :=
match b with
| BTrue ⇒ true
| BFalse ⇒ false
| BEq a1 a2 ⇒ (aeval st a1) =? (aeval st a2)
| BLe a1 a2 ⇒ (aeval st a1) <=? (aeval st a2)
| BNot b1 ⇒ negb (beval st b1)
| BAnd b1 b2 ⇒ andb (beval st b1) (beval st b2)
end.
We specialize our notation for total maps to the specific case of states, i.e. using { --> 0 } as empty state.
Notation "{ a --> x }" :=
(t_update { --> 0 } a x) (at level 0).
Notation "{ a --> x ; b --> y }" :=
(t_update ({ a --> x }) b y) (at level 0).
Notation "{ a --> x ; b --> y ; c --> z }" :=
(t_update ({ a --> x ; b --> y }) c z) (at level 0).
Notation "{ a --> x ; b --> y ; c --> z ; d --> t }" :=
(t_update ({ a --> x ; b --> y ; c --> z }) d t) (at level 0).
Notation "{ a --> x ; b --> y ; c --> z ; d --> t ; e --> u }" :=
(t_update ({ a --> x ; b --> y ; c --> z ; d --> t }) e u) (at level 0).
Notation "{ a --> x ; b --> y ; c --> z ; d --> t ; e --> u ; f --> v }" :=
(t_update ({ a --> x ; b --> y ; c --> z ; d --> t ; e --> u }) f v) (at level 0).
(t_update { --> 0 } a x) (at level 0).
Notation "{ a --> x ; b --> y }" :=
(t_update ({ a --> x }) b y) (at level 0).
Notation "{ a --> x ; b --> y ; c --> z }" :=
(t_update ({ a --> x ; b --> y }) c z) (at level 0).
Notation "{ a --> x ; b --> y ; c --> z ; d --> t }" :=
(t_update ({ a --> x ; b --> y ; c --> z }) d t) (at level 0).
Notation "{ a --> x ; b --> y ; c --> z ; d --> t ; e --> u }" :=
(t_update ({ a --> x ; b --> y ; c --> z ; d --> t }) e u) (at level 0).
Notation "{ a --> x ; b --> y ; c --> z ; d --> t ; e --> u ; f --> v }" :=
(t_update ({ a --> x ; b --> y ; c --> z ; d --> t ; e --> u }) f v) (at level 0).
Commands
Syntax
c ::= SKIP | x ::= a | c ;; c | IFB b THEN c ELSE c FI
| WHILE b DO c END
| WHILE b DO c END
Z ::= X;;
Y ::= 1;;
WHILE ! (Z = 0) DO
Y ::= Y * Z;;
Z ::= Z - 1
END
When this command terminates, the variable Y will contain the
factorial of the initial value of X.
Y ::= 1;;
WHILE ! (Z = 0) DO
Y ::= Y * Z;;
Z ::= Z - 1
END
Inductive com : Type :=
| CSkip
| CAss (x : string) (a : aexp)
| CSeq (c1 c2 : com)
| CIf (b : bexp) (c1 c2 : com)
| CWhile (b : bexp) (c : com).
As for expressions, we can use a few Notation declarations to make reading and writing Imp programs more convenient.
Bind Scope com_scope with com.
Notation "'SKIP'" :=
CSkip : com_scope.
Notation "x '::=' a" :=
(CAss x a) (at level 60) : com_scope.
Notation "c1 ;; c2" :=
(CSeq c1 c2) (at level 80, right associativity) : com_scope.
Notation "'WHILE' b 'DO' c 'END'" :=
(CWhile b c) (at level 80, right associativity) : com_scope.
Notation "'IFB' c1 'THEN' c2 'ELSE' c3 'FI'" :=
(CIf c1 c2 c3) (at level 80, right associativity) : com_scope.
Open Scope com_scope.
Notation "'SKIP'" :=
CSkip : com_scope.
Notation "x '::=' a" :=
(CAss x a) (at level 60) : com_scope.
Notation "c1 ;; c2" :=
(CSeq c1 c2) (at level 80, right associativity) : com_scope.
Notation "'WHILE' b 'DO' c 'END'" :=
(CWhile b c) (at level 80, right associativity) : com_scope.
Notation "'IFB' c1 'THEN' c2 'ELSE' c3 'FI'" :=
(CIf c1 c2 c3) (at level 80, right associativity) : com_scope.
Open Scope com_scope.
Imp Factorial in Coq
For example, here is the factorial function again, written as a formal definition to Coq:
Definition fact_in_coq : com :=
Z ::= X;;
Y ::= 1;;
WHILE ! (Z = 0) DO
Y ::= Y * Z;;
Z ::= Z - 1
END.
Z ::= X;;
Y ::= 1;;
WHILE ! (Z = 0) DO
Y ::= Y * Z;;
Z ::= Z - 1
END.
Definition plus2 : com :=
X ::= X + 2.
Definition XtimesYinZ : com :=
Z ::= X * Y.
Definition subtract_slowly_body : com :=
Z ::= Z - 1 ;;
X ::= X - 1.
X ::= X + 2.
Definition XtimesYinZ : com :=
Z ::= X * Y.
Definition subtract_slowly_body : com :=
Z ::= Z - 1 ;;
X ::= X - 1.
Definition subtract_slowly : com :=
WHILE ! (X = 0) DO
subtract_slowly_body
END.
Definition subtract_3_from_5_slowly : com :=
X ::= 3 ;;
Z ::= 5 ;;
subtract_slowly.
Definition loop : com :=
WHILE true DO
SKIP
END.
Evaluating Commands
Evaluation as a Function (Failed Attempt)
Fixpoint ceval_fun_no_while (st : state) (c : com)
: state :=
match c with
| SKIP ⇒
st
| x ::= a1 ⇒
st & { x --> (aeval st a1) }
| c1 ;; c2 ⇒
let st' := ceval_fun_no_while st c1 in
ceval_fun_no_while st' c2
| IFB b THEN c1 ELSE c2 FI ⇒
if (beval st b)
then ceval_fun_no_while st c1
else ceval_fun_no_while st c2
| WHILE b DO c END ⇒
st (* bogus *)
end.
: state :=
match c with
| SKIP ⇒
st
| x ::= a1 ⇒
st & { x --> (aeval st a1) }
| c1 ;; c2 ⇒
let st' := ceval_fun_no_while st c1 in
ceval_fun_no_while st' c2
| IFB b THEN c1 ELSE c2 FI ⇒
if (beval st b)
then ceval_fun_no_while st c1
else ceval_fun_no_while st c2
| WHILE b DO c END ⇒
st (* bogus *)
end.
Evaluation as a Relation
Operational Semantics
(E_Skip) | |
SKIP / st \\ st |
aeval st a1 = n | (E_Ass) |
x := a1 / st \\ st & { x --> n } |
c1 / st \\ st' | |
c2 / st' \\ st'' | (E_Seq) |
c1;;c2 / st \\ st'' |
beval st b1 = true | |
c1 / st \\ st' | (E_IfTrue) |
IF b1 THEN c1 ELSE c2 FI / st \\ st' |
beval st b1 = false | |
c2 / st \\ st' | (E_IfFalse) |
IF b1 THEN c1 ELSE c2 FI / st \\ st' |
beval st b = false | (E_WhileFalse) |
WHILE b DO c END / st \\ st |
beval st b = true | |
c / st \\ st' | |
WHILE b DO c END / st' \\ st'' | (E_WhileTrue) |
WHILE b DO c END / st \\ st'' |
Reserved Notation "c1 '/' st '\\' st'"
(at level 40, st at level 39).
Inductive ceval : com → state → state → Prop :=
| E_Skip : ∀ st,
SKIP / st \\ st
| E_Ass : ∀ st a1 n x,
aeval st a1 = n →
(x ::= a1) / st \\ st & { x --> n }
| E_Seq : ∀ c1 c2 st st' st'',
c1 / st \\ st' →
c2 / st' \\ st'' →
(c1 ;; c2) / st \\ st''
| E_IfTrue : ∀ st st' b c1 c2,
beval st b = true →
c1 / st \\ st' →
(IFB b THEN c1 ELSE c2 FI) / st \\ st'
| E_IfFalse : ∀ st st' b c1 c2,
beval st b = false →
c2 / st \\ st' →
(IFB b THEN c1 ELSE c2 FI) / st \\ st'
| E_WhileFalse : ∀ b st c,
beval st b = false →
(WHILE b DO c END) / st \\ st
| E_WhileTrue : ∀ st st' st'' b c,
beval st b = true →
c / st \\ st' →
(WHILE b DO c END) / st' \\ st'' →
(WHILE b DO c END) / st \\ st''
where "c1 '/' st '\\' st'" := (ceval c1 st st').
The cost of defining evaluation as a relation instead of a function is that we now need to construct proofs that some program evaluates to some result state, rather than just letting Coq's computation mechanism do it for us.
Example ceval_example1:
(X ::= 2;;
IFB X ≤ 1
THEN Y ::= 3
ELSE Z ::= 4
FI)
/ { --> 0 } \\ { X --> 2 ; Z --> 4 }.
Proof.
(* WORK IN CLASS *) Admitted.
(X ::= 2;;
IFB X ≤ 1
THEN Y ::= 3
ELSE Z ::= 4
FI)
/ { --> 0 } \\ { X --> 2 ; Z --> 4 }.
Proof.
(* WORK IN CLASS *) Admitted.
Is the following proposition provable?
(2) No
(3) Not sure
∀ c st st',
(SKIP ;; c) / st \\ st' →
c / st \\ st'
(1) Yes
(SKIP ;; c) / st \\ st' →
c / st \\ st'
Is the following proposition provable?
(2) No
(3) Not sure
∀ c1 c2 st st',
(c1;;c2) / st \\ st' →
c1 / st \\ st →
c2 / st \\ st'
(1) Yes
(c1;;c2) / st \\ st' →
c1 / st \\ st →
c2 / st \\ st'
Is the following proposition provable?
(2) No
(3) Not sure
∀ b c st st',
(IFB b THEN c ELSE c FI) / st \\ st' →
c / st \\ st'
(1) Yes
(IFB b THEN c ELSE c FI) / st \\ st' →
c / st \\ st'
Is the following proposition provable?
(2) No
(3) Not sure
∀ b,
(∀ st, beval st b = true) →
∀ c st,
~(∃ st', (WHILE b DO c END) / st \\ st')
(1) Yes
(∀ st, beval st b = true) →
∀ c st,
~(∃ st', (WHILE b DO c END) / st \\ st')
Is the following proposition provable?
(2) No
(3) Not sure
∀ b c st,
~(∃ st', (WHILE b DO c END) / st \\ st') →
∀ st'', beval st'' b = true
(1) Yes
~(∃ st', (WHILE b DO c END) / st \\ st') →
∀ st'', beval st'' b = true
Theorem ceval_deterministic: ∀ c st st1 st2,
c / st \\ st1 →
c / st \\ st2 →
st1 = st2.
Proof.
intros c st st1 st2 E1 E2.
generalize dependent st2.
induction E1;
intros st2 E2; inversion E2; subst.
- (* E_Skip *) reflexivity.
- (* E_Ass *) reflexivity.
- (* E_Seq *)
assert (st' = st'0) as EQ1.
{ (* Proof of assertion *) apply IHE1_1; assumption. }
subst st'0.
apply IHE1_2. assumption.
- (* E_IfTrue, b1 evaluates to true *)
apply IHE1. assumption.
- (* E_IfTrue, b1 evaluates to false (contradiction) *)
rewrite H in H5. discriminate H5.
- (* E_IfFalse, b1 evaluates to true (contradiction) *)
rewrite H in H5. discriminate H5.
- (* E_IfFalse, b1 evaluates to false *)
apply IHE1. assumption.
- (* E_WhileFalse, b1 evaluates to false *)
reflexivity.
- (* E_WhileFalse, b1 evaluates to true (contradiction) *)
rewrite H in H2. discriminate H2.
- (* E_WhileTrue, b1 evaluates to false (contradiction) *)
rewrite H in H4. discriminate H4.
- (* E_WhileTrue, b1 evaluates to true *)
assert (st' = st'0) as EQ1.
{ (* Proof of assertion *) apply IHE1_1; assumption. }
subst st'0.
apply IHE1_2. assumption. Qed.
intros c st st1 st2 E1 E2.
generalize dependent st2.
induction E1;
intros st2 E2; inversion E2; subst.
- (* E_Skip *) reflexivity.
- (* E_Ass *) reflexivity.
- (* E_Seq *)
assert (st' = st'0) as EQ1.
{ (* Proof of assertion *) apply IHE1_1; assumption. }
subst st'0.
apply IHE1_2. assumption.
- (* E_IfTrue, b1 evaluates to true *)
apply IHE1. assumption.
- (* E_IfTrue, b1 evaluates to false (contradiction) *)
rewrite H in H5. discriminate H5.
- (* E_IfFalse, b1 evaluates to true (contradiction) *)
rewrite H in H5. discriminate H5.
- (* E_IfFalse, b1 evaluates to false *)
apply IHE1. assumption.
- (* E_WhileFalse, b1 evaluates to false *)
reflexivity.
- (* E_WhileFalse, b1 evaluates to true (contradiction) *)
rewrite H in H2. discriminate H2.
- (* E_WhileTrue, b1 evaluates to false (contradiction) *)
rewrite H in H4. discriminate H4.
- (* E_WhileTrue, b1 evaluates to true *)
assert (st' = st'0) as EQ1.
{ (* Proof of assertion *) apply IHE1_1; assumption. }
subst st'0.
apply IHE1_2. assumption. Qed.