Lecture notes 20190326
Denotational Semantics 1
Remark. Some material in this lecture is from << Software Foundation >>
volume 1 and volume 2.
Require Import PL.Imp6.
Review: Program Expression's Denotational Semantics
Fixpoint aeval (a : aexp) (st : state) : Z :=
match a with
| ANum n ⇒ n
| AId X ⇒ st X (* <----- the value of X on program state st *)
| APlus a1 a2 ⇒ (aeval a1 st) + (aeval a2 st)
| AMinus a1 a2 ⇒ (aeval a1 st) - (aeval a2 st)
| AMult a1 a2 ⇒ (aeval a1 st) * (aeval a2 st)
end.
match a with
| ANum n ⇒ n
| AId X ⇒ st X (* <----- the value of X on program state st *)
| APlus a1 a2 ⇒ (aeval a1 st) + (aeval a2 st)
| AMinus a1 a2 ⇒ (aeval a1 st) - (aeval a2 st)
| AMult a1 a2 ⇒ (aeval a1 st) * (aeval a2 st)
end.
This time, we swap the order of two arguments. As a result, aexp_eval
can be interpreted as a two-argument function, or a one-argument function which
maps integer expresions into functions from program states to integers. In
other words, the denotation of an integer expression a is a function from
program states to integer values. To be more explicit, we can redefine it as
follows.
Definition dadd (d1 d2: state → Z): state → Z :=
fun st ⇒ d1 st + d2 st.
fun st ⇒ d1 st + d2 st.
Here, we define dadd to be the sum of two functions. In Coq, we use
fun st ⇒ ... to represent a function which takes st as its argument. The
right hand side expression of fun st ⇒ represents the function value given
this specific argument st. In summary, dadd is a function which takes two
arguments. These two arguments and the function value are themselves functions.
The function value is define using fun st ⇒ ... in Coq.
Definition dsub (d1 d2: state → Z): state → Z :=
fun st ⇒ d1 st - d2 st.
Definition dmul (d1 d2: state → Z): state → Z :=
fun st ⇒ d1 st * d2 st.
fun st ⇒ d1 st - d2 st.
Definition dmul (d1 d2: state → Z): state → Z :=
fun st ⇒ d1 st * d2 st.
Similarly, we can define the subtraction and multiplication of two
functions. Then, the denotation of an aexp can be defined as:
Fixpoint aeval' (a : aexp) : state → Z :=
match a with
| ANum n ⇒ fun _ ⇒ n
| AId X ⇒ fun st ⇒ st X
| APlus a1 a2 ⇒ dadd (aeval' a1) (aeval' a2)
| AMinus a1 a2 ⇒ dsub (aeval' a1) (aeval' a2)
| AMult a1 a2 ⇒ dmul (aeval' a1) (aeval' a2)
end.
match a with
| ANum n ⇒ fun _ ⇒ n
| AId X ⇒ fun st ⇒ st X
| APlus a1 a2 ⇒ dadd (aeval' a1) (aeval' a2)
| AMinus a1 a2 ⇒ dsub (aeval' a1) (aeval' a2)
| AMult a1 a2 ⇒ dmul (aeval' a1) (aeval' a2)
end.
These two definitions aeval and aeval' are obviously equivalent. We can
prove it in Coq.
Fact aeval_aeval': ∀a st, aeval a st = aeval' a st.
Proof.
intros.
Proof.
intros.
To prove such a property, we would like to do induction over the syntax
tree of a.
induction a.
+ simpl.
reflexivity.
+ simpl.
reflexivity.
+ simpl.
rewrite IHa1.
rewrite IHa2.
unfold dadd.
reflexivity.
+ simpl.
rewrite IHa1.
rewrite IHa2.
unfold dsub.
reflexivity.
+ simpl.
rewrite IHa1.
rewrite IHa2.
unfold dmul.
reflexivity.
Qed.
+ simpl.
reflexivity.
+ simpl.
reflexivity.
+ simpl.
rewrite IHa1.
rewrite IHa2.
unfold dadd.
reflexivity.
+ simpl.
rewrite IHa1.
rewrite IHa2.
unfold dsub.
reflexivity.
+ simpl.
rewrite IHa1.
rewrite IHa2.
unfold dmul.
reflexivity.
Qed.
Expression Equivalence
Definition aexp_dequiv (d1 d2: state → Z): Prop :=
∀st, d1 st = d2 st.
Definition aexp_equiv (a1 a2: aexp): Prop :=
aexp_dequiv (aeval a1) (aeval a2).
∀st, d1 st = d2 st.
Definition aexp_equiv (a1 a2: aexp): Prop :=
aexp_dequiv (aeval a1) (aeval a2).
Definition is another keyword for writing definitions in Coq besides
Fixpoint. It can beused to define functions and/or values but recursions are
not allowed in such definitions.
Here, we define that two aexps are behaviorally equivalent if they
evaluate to the same result in every state. We first define the equivalence
relation between two denotations (which are functions from program states to
integers). Then, we define that two integer expressions are equivalent if their
denotations are equivalent. Here are some examples.
Module aexp_equiv_example.
Import Abstract_Pretty_Printing.
Example ex1: ∀(X: var), aexp_equiv (X - X) 0.
Proof.
unfold aexp_equiv, aexp_dequiv.
intros.
simpl.
omega.
Qed.
Example ex2: ∀(X: var), aexp_equiv (X + X) (X * 2).
Proof.
unfold aexp_equiv, aexp_dequiv.
intros.
simpl.
omega.
Qed.
End aexp_equiv_example.
Import Abstract_Pretty_Printing.
Example ex1: ∀(X: var), aexp_equiv (X - X) 0.
Proof.
unfold aexp_equiv, aexp_dequiv.
intros.
simpl.
omega.
Qed.
Example ex2: ∀(X: var), aexp_equiv (X + X) (X * 2).
Proof.
unfold aexp_equiv, aexp_dequiv.
intros.
simpl.
omega.
Qed.
End aexp_equiv_example.
The Constant-Folding Transformation
Fixpoint fold_constants_aexp (a : aexp) : aexp :=
match a with
| ANum n ⇒ ANum n
| AId x ⇒ AId x
| APlus a1 a2 ⇒
match fold_constants_aexp a1, fold_constants_aexp a2 with
| ANum n1, ANum n2 ⇒ ANum (n1 + n2)
| _, _ ⇒ APlus (fold_constants_aexp a1) (fold_constants_aexp a2)
end
| AMinus a1 a2 ⇒
match fold_constants_aexp a1, fold_constants_aexp a2 with
| ANum n1, ANum n2 ⇒ ANum (n1 - n2)
| _, _ ⇒ AMinus (fold_constants_aexp a1) (fold_constants_aexp a2)
end
| AMult a1 a2 ⇒
match fold_constants_aexp a1, fold_constants_aexp a2 with
| ANum n1, ANum n2 ⇒ ANum (n1 * n2)
| _, _ ⇒ AMult (fold_constants_aexp a1) (fold_constants_aexp a2)
end
end.
match a with
| ANum n ⇒ ANum n
| AId x ⇒ AId x
| APlus a1 a2 ⇒
match fold_constants_aexp a1, fold_constants_aexp a2 with
| ANum n1, ANum n2 ⇒ ANum (n1 + n2)
| _, _ ⇒ APlus (fold_constants_aexp a1) (fold_constants_aexp a2)
end
| AMinus a1 a2 ⇒
match fold_constants_aexp a1, fold_constants_aexp a2 with
| ANum n1, ANum n2 ⇒ ANum (n1 - n2)
| _, _ ⇒ AMinus (fold_constants_aexp a1) (fold_constants_aexp a2)
end
| AMult a1 a2 ⇒
match fold_constants_aexp a1, fold_constants_aexp a2 with
| ANum n1, ANum n2 ⇒ ANum (n1 * n2)
| _, _ ⇒ AMult (fold_constants_aexp a1) (fold_constants_aexp a2)
end
end.
Here, we see that the match expressions in Coq are very flexible. (1) We
can apply pattern matching on not only Coq variables but also any Coq expression
whose type is inductively defined. (2) We can apply pattern matching on two
expressions at the same time. (3) We can use underscore _ to cover default
cases.
Module fold_const_example.
Import Abstract_Pretty_Printing.
Example ex1 : ∀(X: var),
fold_constants_aexp ((1 + 2) * X)
= (3 * X)%imp.
Proof. intros. reflexivity. Qed.
Import Abstract_Pretty_Printing.
Example ex1 : ∀(X: var),
fold_constants_aexp ((1 + 2) * X)
= (3 * X)%imp.
Proof. intros. reflexivity. Qed.
Note that this version of constant folding doesn't eliminate trivial
additions, etc. — we are focusing attention on a single optimization for the
sake of simplicity. It is not hard to incorporate other ways of simplifying
expressions; the definitions and proofs just get longer.
Example fold_aexp_ex2 : ∀(X: var) (Y: var),
fold_constants_aexp (X - ((0 * 6) + Y))%imp = (X - (0 + Y))%imp.
Proof. intros. reflexivity. Qed.
End fold_const_example.
fold_constants_aexp (X - ((0 * 6) + Y))%imp = (X - (0 + Y))%imp.
Proof. intros. reflexivity. Qed.
End fold_const_example.
Theorem fold_constants_aexp_sound : ∀a,
aexp_equiv a (fold_constants_aexp a).
Proof.
unfold aexp_equiv, aexp_dequiv. intros.
aexp_equiv a (fold_constants_aexp a).
Proof.
unfold aexp_equiv, aexp_dequiv. intros.
To prove such a property, we would like to do induction over the syntax
tree of a.
induction a.
+ (* ANum case *)
simpl.
reflexivity.
+ (* AId case *)
simpl.
reflexivity.
+ (* APlus case *)
simpl.
+ (* ANum case *)
simpl.
reflexivity.
+ (* AId case *)
simpl.
reflexivity.
+ (* APlus case *)
simpl.
Here, we have to do case analysis on fold_constants_aexp a1 and
fold_constants_aexp a2. The tactic destruct can be used to accomplish
this. The following line turns the current proof goal into five goals, each
of which corresponds to one different kind of fold_constants_aexp a1.
The eqn clauses in the end indicates that an extra assumption Ha1 will
be generated describing what does fold_constants_aexp a1 equal to.
destruct (fold_constants_aexp a1) eqn:Ha1.
- (** In this first case, Ha1 says that fold_constants_aexp a1 is a
constant. In this case, we need another destruct for a2. *)
destruct (fold_constants_aexp a2) eqn:Ha2.
* rewrite IHa1.
rewrite IHa2.
simpl.
reflexivity.
* rewrite IHa1.
rewrite IHa2.
simpl.
reflexivity.
* rewrite IHa1.
rewrite IHa2.
simpl.
reflexivity.
* rewrite IHa1.
rewrite IHa2.
simpl.
reflexivity.
* rewrite IHa1.
rewrite IHa2.
simpl.
reflexivity.
- rewrite IHa1.
rewrite IHa2.
simpl.
reflexivity.
- rewrite IHa1.
rewrite IHa2.
simpl.
reflexivity.
- rewrite IHa1.
rewrite IHa2.
simpl.
reflexivity.
- rewrite IHa1.
rewrite IHa2.
simpl.
reflexivity.
+ (* AMinus case *)
simpl.
- (** In this first case, Ha1 says that fold_constants_aexp a1 is a
constant. In this case, we need another destruct for a2. *)
destruct (fold_constants_aexp a2) eqn:Ha2.
* rewrite IHa1.
rewrite IHa2.
simpl.
reflexivity.
* rewrite IHa1.
rewrite IHa2.
simpl.
reflexivity.
* rewrite IHa1.
rewrite IHa2.
simpl.
reflexivity.
* rewrite IHa1.
rewrite IHa2.
simpl.
reflexivity.
* rewrite IHa1.
rewrite IHa2.
simpl.
reflexivity.
- rewrite IHa1.
rewrite IHa2.
simpl.
reflexivity.
- rewrite IHa1.
rewrite IHa2.
simpl.
reflexivity.
- rewrite IHa1.
rewrite IHa2.
simpl.
reflexivity.
- rewrite IHa1.
rewrite IHa2.
simpl.
reflexivity.
+ (* AMinus case *)
simpl.
In the previous case, we duplicate our proof script for many times.
The tactic language in Coq provide semicolon ;. Specifically,
tac1 ; tac2 says do tac1, then do tac2 in every single proof goal
that tac1 generates. Semicolon is right associative.
destruct (fold_constants_aexp a1) eqn:Ha1;
destruct (fold_constants_aexp a2) eqn:Ha2;
rewrite IHa1;
rewrite IHa2;
reflexivity.
+ (* AMult case *)
simpl.
destruct (fold_constants_aexp a1) eqn:Ha1;
destruct (fold_constants_aexp a2) eqn:Ha2;
rewrite IHa1;
rewrite IHa2;
reflexivity.
Qed.
destruct (fold_constants_aexp a2) eqn:Ha2;
rewrite IHa1;
rewrite IHa2;
reflexivity.
+ (* AMult case *)
simpl.
destruct (fold_constants_aexp a1) eqn:Ha1;
destruct (fold_constants_aexp a2) eqn:Ha2;
rewrite IHa1;
rewrite IHa2;
reflexivity.
Qed.
After proving this soundness property, we want to prove that this
optimization really improves something. For stating this property formally, we
first need to define the steps of calculation involved in program expressions.
Fixpoint cal_count (a : aexp) : Z :=
match a with
| ANum n ⇒ 0
| AId X ⇒ 0
| APlus a1 a2 ⇒ (cal_count a1) + (cal_count a2) + 1
| AMinus a1 a2 ⇒ (cal_count a1) + (cal_count a2) + 1
| AMult a1 a2 ⇒ (cal_count a1) + (cal_count a2) + 1
end.
match a with
| ANum n ⇒ 0
| AId X ⇒ 0
| APlus a1 a2 ⇒ (cal_count a1) + (cal_count a2) + 1
| AMinus a1 a2 ⇒ (cal_count a1) + (cal_count a2) + 1
| AMult a1 a2 ⇒ (cal_count a1) + (cal_count a2) + 1
end.
Now, we can prove that an optimized expression never takes more calculation
than the original one.
Lemma fold_constants_aexp_improve : ∀a,
cal_count (fold_constants_aexp a) ≤ cal_count a.
Proof.
intros.
induction a.
+ simpl.
omega.
+ simpl.
omega.
+ simpl.
destruct (fold_constants_aexp a1) eqn:Ha1;
destruct (fold_constants_aexp a2) eqn:Ha2;
simpl in IHa1;
simpl in IHa2;
simpl;
omega.
+ simpl.
destruct (fold_constants_aexp a1) eqn:Ha1;
destruct (fold_constants_aexp a2) eqn:Ha2;
simpl in IHa1;
simpl in IHa2;
simpl;
omega.
+ simpl.
destruct (fold_constants_aexp a1) eqn:Ha1;
destruct (fold_constants_aexp a2) eqn:Ha2;
simpl in IHa1;
simpl in IHa2;
simpl;
omega.
Qed.
cal_count (fold_constants_aexp a) ≤ cal_count a.
Proof.
intros.
induction a.
+ simpl.
omega.
+ simpl.
omega.
+ simpl.
destruct (fold_constants_aexp a1) eqn:Ha1;
destruct (fold_constants_aexp a2) eqn:Ha2;
simpl in IHa1;
simpl in IHa2;
simpl;
omega.
+ simpl.
destruct (fold_constants_aexp a1) eqn:Ha1;
destruct (fold_constants_aexp a2) eqn:Ha2;
simpl in IHa1;
simpl in IHa2;
simpl;
omega.
+ simpl.
destruct (fold_constants_aexp a1) eqn:Ha1;
destruct (fold_constants_aexp a2) eqn:Ha2;
simpl in IHa1;
simpl in IHa2;
simpl;
omega.
Qed.
Evaluating Command
Definition com_dequiv (d1 d2: state → state → Prop): Prop :=
∀st1 st2, d1 st1 st2 ↔ d2 st1 st2.
∀st1 st2, d1 st1 st2 ↔ d2 st1 st2.
A set of program state pairs is also called a binary relation between
program states. In Coq, we can use state → state → Prop to present such
type. As a preparation, we first define some basic concepts about relations.
Module Relation_Operators.
Definition id {A: Type}: A → A → Prop := fun a b ⇒ a = b.
Definition empty {A B: Type}: A → B → Prop := fun a b ⇒ False.
Definition concat {A B C: Type} (r1: A → B → Prop) (r2: B → C → Prop): A → C → Prop :=
fun a c ⇒ ∃b, r1 a b ∧ r2 b c.
Definition filter1 {A B: Type} (f: A → Prop): A → B → Prop :=
fun a b ⇒ f a.
Definition filter2 {A B: Type} (f: B → Prop): A → B → Prop :=
fun a b ⇒ f b.
Definition union {A B: Type} (r1 r2: A → B → Prop): A → B → Prop :=
fun a b ⇒ r1 a b ∨ r2 a b.
Definition intersection {A B: Type} (r1 r2: A → B → Prop): A → B → Prop :=
fun a b ⇒ r1 a b ∧ r2 a b.
Definition id {A: Type}: A → A → Prop := fun a b ⇒ a = b.
Definition empty {A B: Type}: A → B → Prop := fun a b ⇒ False.
Definition concat {A B C: Type} (r1: A → B → Prop) (r2: B → C → Prop): A → C → Prop :=
fun a c ⇒ ∃b, r1 a b ∧ r2 b c.
Definition filter1 {A B: Type} (f: A → Prop): A → B → Prop :=
fun a b ⇒ f a.
Definition filter2 {A B: Type} (f: B → Prop): A → B → Prop :=
fun a b ⇒ f b.
Definition union {A B: Type} (r1 r2: A → B → Prop): A → B → Prop :=
fun a b ⇒ r1 a b ∨ r2 a b.
Definition intersection {A B: Type} (r1 r2: A → B → Prop): A → B → Prop :=
fun a b ⇒ r1 a b ∧ r2 a b.
In these definitions, we sometimes use braces "{}" instead of parentheses
"()". When braces are used, those arguments are called implicit arguments, i.e.
you do not need to write those arguments when you use a function.
End Relation_Operators.
Import Relation_Operators.
Fixpoint beval (b : bexp) (st : state) : Prop :=
match b with
| BTrue ⇒ True
| BFalse ⇒ False
| BEq a1 a2 ⇒ (aeval a1 st) = (aeval a2 st)
| BLe a1 a2 ⇒ (aeval a1 st) ≤ (aeval a2 st)
| BNot b1 ⇒ ¬(beval b1 st)
| BAnd b1 b2 ⇒ (beval b1 st) ∧ (beval b2 st)
end.
Arguments beval b st: simpl never.
Fixpoint loop_free_ceval (c: com): state → state → Prop :=
match c with
| CSkip ⇒ id
| CAss X E ⇒
fun st1 st2 ⇒ st2 X = aeval E st1 ∧
∀Y, X ≠ Y → st1 Y = st2 Y
| CSeq c1 c2 ⇒
concat
(loop_free_ceval c1) (loop_free_ceval c2)
| CIf b c1 c2 ⇒
union
(intersection
(loop_free_ceval c1)
(filter1 (beval b)))
(intersection
(loop_free_ceval c2)
(filter1 (beval (BNot b))))
| CWhile _ _ ⇒ empty
end.
Import Relation_Operators.
Fixpoint beval (b : bexp) (st : state) : Prop :=
match b with
| BTrue ⇒ True
| BFalse ⇒ False
| BEq a1 a2 ⇒ (aeval a1 st) = (aeval a2 st)
| BLe a1 a2 ⇒ (aeval a1 st) ≤ (aeval a2 st)
| BNot b1 ⇒ ¬(beval b1 st)
| BAnd b1 b2 ⇒ (beval b1 st) ∧ (beval b2 st)
end.
Arguments beval b st: simpl never.
Fixpoint loop_free_ceval (c: com): state → state → Prop :=
match c with
| CSkip ⇒ id
| CAss X E ⇒
fun st1 st2 ⇒ st2 X = aeval E st1 ∧
∀Y, X ≠ Y → st1 Y = st2 Y
| CSeq c1 c2 ⇒
concat
(loop_free_ceval c1) (loop_free_ceval c2)
| CIf b c1 c2 ⇒
union
(intersection
(loop_free_ceval c1)
(filter1 (beval b)))
(intersection
(loop_free_ceval c2)
(filter1 (beval (BNot b))))
| CWhile _ _ ⇒ empty
end.
Using these basic definitions about relation, we can easily define the
denotation of empty commands, assignment commands, sequential composition and
if-then-else commands. We hope that the evaluation function ceval has the
following property:
Here, we first demonstrate a concrete semantic definition which will fullfill
our requirement. Afterwards, we will introduce the general theory for
Bourbaki-Witt fixpoint.
The following recursive function defines the semantics of executing the loop
body for exactly n times. In Coq, nat represents nature numbers. Coq users
can write functions recursively on nature numbers as recursively defined on
lists. Specifically, a natural number n is either zero O (the "O" of
Omega) or the successor of another natural number n', written as S n'.
ceval (CWhile b c) =
union
(intersection
(concat loop_body
(ceval (CWhile b c)))
(filter1 (beval b)))
(intersection
id
(filter1 (beval (BNot b))))
But it is not obvious to find out a definition that satisfies this property. We
define it using Bourbaki-Witt fixpoint theorem.
union
(intersection
(concat loop_body
(ceval (CWhile b c)))
(filter1 (beval b)))
(intersection
id
(filter1 (beval (BNot b))))
Fixpoint iter_loop_body (b: bexp)
(loop_body: state → state → Prop)
(n: nat): state → state → Prop :=
match n with
| O ⇒
intersection
id
(filter1 (beval (BNot b)))
| S n' ⇒
intersection
(concat
loop_body
(iter_loop_body b loop_body n'))
(filter1 (beval b))
end.
(loop_body: state → state → Prop)
(n: nat): state → state → Prop :=
match n with
| O ⇒
intersection
id
(filter1 (beval (BNot b)))
| S n' ⇒
intersection
(concat
loop_body
(iter_loop_body b loop_body n'))
(filter1 (beval b))
end.
In short, it says iter_loop_body b loop_body n is defined as:
The union of these binary relations is exactly the meaning of while loops.
The following relation operator omega_union defines the union of countably
many relations.
- if n = 0, identity relation with the restriction that b is not true;
- if n = n' + 1, first do loop_body then do iter_loop_body b loop_body n' with the restriction that b is true at beginning.
Module Relation_Operators2.
Definition omega_union {A: Type} (rs: nat → A → A → Prop): A → A → Prop :=
fun st1 st2 ⇒ ∃n, rs n st1 st2.
End Relation_Operators2.
Import Relation_Operators2.
Definition loop_sem (b: bexp) (loop_body: state → state → Prop):
state → state → Prop :=
omega_union (iter_loop_body b loop_body).
Definition omega_union {A: Type} (rs: nat → A → A → Prop): A → A → Prop :=
fun st1 st2 ⇒ ∃n, rs n st1 st2.
End Relation_Operators2.
Import Relation_Operators2.
Definition loop_sem (b: bexp) (loop_body: state → state → Prop):
state → state → Prop :=
omega_union (iter_loop_body b loop_body).
And we can prove that this definition satisfies the recursive equation that
we want.
Theorem loop_recur: ∀b loop_body,
com_dequiv
(loop_sem b loop_body)
(union
(intersection
(concat loop_body
(loop_sem b loop_body))
(filter1 (beval b)))
(intersection
id
(filter1 (beval (BNot b))))).
Proof.
intros.
unfold com_dequiv.
intros.
split.
+ intros.
unfold loop_sem, omega_union in H.
unfold union.
destruct H as [n H].
com_dequiv
(loop_sem b loop_body)
(union
(intersection
(concat loop_body
(loop_sem b loop_body))
(filter1 (beval b)))
(intersection
id
(filter1 (beval (BNot b))))).
Proof.
intros.
unfold com_dequiv.
intros.
split.
+ intros.
unfold loop_sem, omega_union in H.
unfold union.
destruct H as [n H].
Now we need to do case analysis over whether n is zero or not.
destruct n as [| n'].
- right.
simpl in H.
exact H.
- left.
simpl in H.
unfold concat, intersection in H.
unfold concat, intersection.
destruct H as [[st' [? ?]] ?].
split.
* ∃st'.
split.
{ exact H. }
unfold loop_sem, omega_union.
∃n'.
exact H0.
* exact H1.
+ intros.
unfold loop_sem, omega_union.
unfold union in H.
destruct H.
- unfold intersection, concat in H.
destruct H as [[st' [? ?]] ?].
unfold loop_sem, omega_union in H0.
destruct H0 as [n ?].
∃(S n).
simpl.
unfold intersection, concat.
split.
* ∃st'.
split.
{ exact H. }
{ exact H0. }
* exact H1.
- ∃O.
simpl.
exact H.
Qed.
- right.
simpl in H.
exact H.
- left.
simpl in H.
unfold concat, intersection in H.
unfold concat, intersection.
destruct H as [[st' [? ?]] ?].
split.
* ∃st'.
split.
{ exact H. }
unfold loop_sem, omega_union.
∃n'.
exact H0.
* exact H1.
+ intros.
unfold loop_sem, omega_union.
unfold union in H.
destruct H.
- unfold intersection, concat in H.
destruct H as [[st' [? ?]] ?].
unfold loop_sem, omega_union in H0.
destruct H0 as [n ?].
∃(S n).
simpl.
unfold intersection, concat.
split.
* ∃st'.
split.
{ exact H. }
{ exact H0. }
* exact H1.
- ∃O.
simpl.
exact H.
Qed.
With loop_sem which is just defined, we are eventually ready to complete
our definition of ceval.
Fixpoint ceval (c: com): state → state → Prop :=
match c with
| CSkip ⇒ id
| CAss X E ⇒
fun st1 st2 ⇒ st2 X = aeval E st1 ∧
∀Y, X ≠ Y → st1 Y = st2 Y
| CSeq c1 c2 ⇒
concat (ceval c1) (ceval c2)
| CIf b c1 c2 ⇒
union
(intersection
(ceval c1) (filter1 (beval b)))
(intersection
(ceval c2) (filter1 (beval (BNot b))))
| CWhile b c ⇒
loop_sem b (ceval c)
end.
match c with
| CSkip ⇒ id
| CAss X E ⇒
fun st1 st2 ⇒ st2 X = aeval E st1 ∧
∀Y, X ≠ Y → st1 Y = st2 Y
| CSeq c1 c2 ⇒
concat (ceval c1) (ceval c2)
| CIf b c1 c2 ⇒
union
(intersection
(ceval c1) (filter1 (beval b)))
(intersection
(ceval c2) (filter1 (beval (BNot b))))
| CWhile b c ⇒
loop_sem b (ceval c)
end.
Bourbaki-Witt Theorem
Partial Order
∀x: A, x ≤ x;
∀x y z: A, x ≤ y → y ≤ z → x ≤ z;
∀x y: A, x ≤ y → y ≤ x → x = y.
∀x y z: A, x ≤ y → y ≤ z → x ≤ z;
∀x y: A, x ≤ y → y ≤ x → x = y.
∀x: A, bot ≤ x
Chain
∀n: nat, xs n ≤ xs (n + 1),
then it forms a chain.
Monotonic and Continuous Functions
∀x y: A, x ≤A= y → F(x) ≤B= F(y).
∀xs: chain(A), lub(F(xs)) = F(lub(xs))
Here, the lub function on the left hand side means the least upper bound
defined by B and the one on the right hand side is defined by A.
Least fixpoint
bot, F(bot), F(F(bot)), F(F(F(bot))), ...
Obviously, bot ≤ F(bot) is true due to the definition of bot. If F is
monotonic, it is immediately followed by F(bot) ≤ F(F(bot)). Similarly,
F(F(bot)) ≤ F(F(F(bot))), F(F(F(bot))) ≤ F(F(F(F(bot)))) ...
In other words, if F is monotonic, this sequence is a chain.
lub [bot, F(bot), F(F(bot)), F(F(F(bot))), ...].
F (lub [bot, F(bot), F(F(bot)), F(F(F(bot))), ...]) =
lub [F(bot), F(F(bot)), F(F(F(bot))), F(F(F(F(bot)))), ...] =
lub [bot, F(bot), F(F(bot)), F(F(F(bot))), ...].
The first equality is true because F is continuous. The second equality is
true because bot is less than or equal to all other elements in the sequence.
lub [F(bot), F(F(bot)), F(F(F(bot))), F(F(F(F(bot)))), ...] =
lub [bot, F(bot), F(F(bot)), F(F(F(bot))), ...].
bot ≤ x
Thus,
F(bot) ≤ F(x) = x
due to the fact that F is monotonic and x is a fixpoint. And so on,
F(F(bot)) ≤ x, F(F(F(bot))) ≤ x, F(F(F(F(bot)))) ≤ x, ...
That means, x is an upper bound of bot, F(bot), F(F(bot)), .... It must be
greater than or equal to
lub [bot, F(bot), F(F(bot)), F(F(F(bot))), ...].
(* Wed Mar 27 17:33:26 UTC 2019 *)