Lecture notes 20210315 Denotational Semantics 2
Remark. Some material in this lecture is from << Software Foundation >>
volume 1 and volume 2. 
Require Import Coq.Classes.RelationClasses.
Require Import Coq.Classes.Morphisms.
Require Import PL.Imp.
Require Import Coq.Classes.Morphisms.
Require Import PL.Imp.
Review: Program Expression's Denotational Semantics
Module AEval_first_try.
Section AEval.
Variable st: state.
Fixpoint aeval (a : aexp) : Z :=
match a with
| ANum n ⇒ n
| AId X ⇒ st X (* <----- the value of X on program state st *)
| APlus a1 a2 ⇒ (aeval a1) + (aeval a2)
| AMinus a1 a2 ⇒ (aeval a1) - (aeval a2)
| AMult a1 a2 ⇒ (aeval a1) * (aeval a2)
end.
End AEval.
Section AEval.
Variable st: state.
Fixpoint aeval (a : aexp) : Z :=
match a with
| ANum n ⇒ n
| AId X ⇒ st X (* <----- the value of X on program state st *)
| APlus a1 a2 ⇒ (aeval a1) + (aeval a2)
| AMinus a1 a2 ⇒ (aeval a1) - (aeval a2)
| AMult a1 a2 ⇒ (aeval a1) * (aeval a2)
end.
End AEval.
Recall that fold_constants_aexp folds constant computation in integer
    expressions. 
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.
Which statements are correct?
 
 
 Answer:
 
 
 
 Let's take another look of our previous definition of aeval. What is its
    Coq type? 
-  1 + 2 + X will be turned into 3 + X;
-  1 - 1 + X will be turned into X;
-  X + 1 - 1 will be turned into X + 0;
- X + 1 * 1 will be turned into X + 1.
-  Correct;
-  Incorrect;
-  Incorrect;
- Correct.
Higher-Order Thinking
Check aeval.
(* aeval : state -> aexp -> Z *)
(* aeval : state -> aexp -> Z *)
It is a binary function. It takes two arguments, a program state and an
    integer expression, then generates one integer value. At the same time, we
    can treate it as a special single-argument function, whose argument type is
    state and whose result type is aexp -> Z, i.e. a function from integer
    expressions to integer values. 
End AEval_first_try.
Module AEval_second_try.
Module AEval_second_try.
Now, let's take another definition. 
Fixpoint aeval (a : aexp) (st : state) : Z :=
match a with
| ANum n ⇒ n
| AId X ⇒ st X
| 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
| 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.
 
    Sometimes we say that 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 via function operations. 
End AEval_second_try.
Module Func1.
Module Func.
Definition add {A: Type} (f g: A -> Z): A -> Z :=
fun a ⇒ f a + g a.
Definition sub {A: Type} (f g: A -> Z): A -> Z :=
fun a ⇒ f a - g a.
Definition mul {A: Type} (f g: A -> Z): A -> Z :=
fun a ⇒ f a * g a.
End Func.
End Func1.
Import Func1.
Declare Scope func_scop.
Delimit Scope func_scope with Func.
Notation "f + g" := (Func.add f g): func_scope.
Notation "f - g" := (Func.sub f g): func_scope.
Notation "f * g" := (Func.mul f g): func_scope.
Module Func1.
Module Func.
Definition add {A: Type} (f g: A -> Z): A -> Z :=
fun a ⇒ f a + g a.
Definition sub {A: Type} (f g: A -> Z): A -> Z :=
fun a ⇒ f a - g a.
Definition mul {A: Type} (f g: A -> Z): A -> Z :=
fun a ⇒ f a * g a.
End Func.
End Func1.
Import Func1.
Declare Scope func_scop.
Delimit Scope func_scope with Func.
Notation "f + g" := (Func.add f g): func_scope.
Notation "f - g" := (Func.sub f g): func_scope.
Notation "f * g" := (Func.mul f g): func_scope.
Here, we define Func.add to be the sum of two functions. In Coq, we use
    fun a ⇒ ... to represent a function which takes st as its argument.
    The right hand side expression of fun a ⇒ represents the function value
    given this specific argument a. In summary, add is a function which
    takes two arguments. These two arguments and the function value are
    themselves functions. The function value is define using fun a ⇒ ... in
    Coq. Functions that manipulate other functions are often called
    higher-order functions (高阶函数). Thus, Func.add is a higher-order
    function. 
 
 In hand written math, we sometimes use f(x) to represent a function and
    sometimes use f(x) to represent a specific value: the result of applying
    function f to a specific value x. Moreover, we write f(x) + g(x) to
    represent the sum of two functions, or two values, which is usually
    unambiguous from context. In comparison, f+g is not used very often.
    In Coq, f x + g x is the sum of two numbers while fun x ⇒ f x + g x is
    the sum of two functions. 
 
 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. For
    using Func.add we do not need to feed that argument A explicitly. Coq
    can infer that from the context. We also define the subtraction and
    multiplication of two functions in a similar way. 
 
 Based on that, the denotation of an aexp can be defined as: 
Definition constant_func {A: Type} (c: Z): A -> Z := fun _ ⇒ c.
Definition query_var (X: var): state -> Z := fun st ⇒ st X.
Fixpoint aeval (a : aexp) : state -> Z :=
match a with
| ANum n ⇒ constant_func n
| AId X ⇒ query_var X
| APlus a1 a2 ⇒ (aeval a1 + aeval a2)%Func
| AMinus a1 a2 ⇒ (aeval a1 - aeval a2)%Func
| AMult a1 a2 ⇒ (aeval a1 * aeval a2)%Func
end.
Definition query_var (X: var): state -> Z := fun st ⇒ st X.
Fixpoint aeval (a : aexp) : state -> Z :=
match a with
| ANum n ⇒ constant_func n
| AId X ⇒ query_var X
| APlus a1 a2 ⇒ (aeval a1 + aeval a2)%Func
| AMinus a1 a2 ⇒ (aeval a1 - aeval a2)%Func
| AMult a1 a2 ⇒ (aeval a1 * aeval a2)%Func
end.
Higher-order thinking is critical here. Remember, the denotation of an
    aexp is a function. Func.add computes the sum of two functions, which
    is used to define the meaning of APlus, the "+" symbol in expressions. 
 
 
More Higher-order Objects
Do-it-three-times
Definition doit3times {X:Type} (f:X->X) (n:X) : X :=
f (f (f n)).
The argument f here is itself a function (from X to
    X); the body of doit3times applies f three times to some
    value n. 
Check @doit3times.
(* ===> doit3times : forall X : Type, (X -> X) -> X -> X *)
Definition minustwo (x: Z): Z := x - 2.
(* ===> doit3times : forall X : Type, (X -> X) -> X -> X *)
Definition minustwo (x: Z): Z := x - 2.
What is the following computations' result? 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
   doit3times minustwo 9
 
   doit3times minustwo (doit3times minustwo 9)
 
   doit3times (doit3times minustwo) 9
 
   (doit3times doit3times) minustwo 9
 
   doit3times (fun n ⇒ n * n) 2
 
   doit3times (Func.add minustwo) (fun x ⇒ x * x) 4
 
   doit3times ((fun x y ⇒ y * y - x * y + x * x) 1) 1
 
Computing sets from functions
Module Func2.
Module Func.
In Coq, we can use A -> Prop to represent subsets of A. Here, a mapping
    from A to Prop means a criterion judging whether every element of A
    is in this set. 
Definition test_eq {A: Type} (f g: A -> Z): A -> Prop :=
fun a ⇒ f a = g a.
Definition test_le {A: Type} (f g: A -> Z): A -> Prop :=
fun a ⇒ f a ≤ g a.
End Func.
End Func2.
Import Func2.
fun a ⇒ f a = g a.
Definition test_le {A: Type} (f g: A -> Z): A -> Prop :=
fun a ⇒ f a ≤ g a.
End Func.
End Func2.
Import Func2.
Module Func3.
Module Func.
Definition equiv {A: Type} (f g: A -> Z): Prop :=
∀a, f a = g a.
Definition le {A: Type} (f g: A -> Z): Prop :=
∀a, f a ≤ g a.
End Func.
End Func3.
Import Func3.
Module Sets1.
Module Sets.
Definition equiv {A: Type} (X Y: A -> Prop): Prop :=
∀a, X a ↔ Y a.
End Sets.
End Sets1.
Import Sets1.
Here is a lemma about function equivalence. 
Lemma Func_add_comm: ∀{A} (f g: A -> Z),
Func.equiv (f + g)%Func (g + f)%Func.
Func.equiv (f + g)%Func (g + f)%Func.
Proof.
(* WORKED IN CLASS *)
intros.
unfold Func.equiv, Func.add.
intros.
lia.
Qed.
(* WORKED IN CLASS *)
intros.
unfold Func.equiv, Func.add.
intros.
lia.
Qed.
Obviously, Func.equiv and Sets.equiv are equivalent relations, i.e. they
    are both reflexive (自反的), symmetric (对称的) and transitive
    (传递的). 
Lemma Func_equiv_refl: ∀A, Reflexive (@Func.equiv A).
Proof.
intros.
unfold Reflexive.
Proof.
intros.
unfold Reflexive.
Reflexive is defined in Coq standard library. We can unfold its
      definitions. 
  unfold Func.equiv.
intros.
reflexivity.
Qed.
Lemma Func_equiv_sym: ∀A, Symmetric (@Func.equiv A).
Lemma Func_equiv_trans: ∀A, Transitive (@Func.equiv A).
Lemma Sets_equiv_refl: ∀A, Reflexive (@Sets.equiv A).
Lemma Sets_equiv_sym: ∀A, Symmetric (@Sets.equiv A).
Lemma Sets_equiv_trans: ∀A, Transitive (@Sets.equiv A).
intros.
reflexivity.
Qed.
Lemma Func_equiv_sym: ∀A, Symmetric (@Func.equiv A).
Proof.
intros.
unfold Symmetric.
unfold Func.equiv.
intros.
rewrite H.
reflexivity.
Qed.
intros.
unfold Symmetric.
unfold Func.equiv.
intros.
rewrite H.
reflexivity.
Qed.
Lemma Func_equiv_trans: ∀A, Transitive (@Func.equiv A).
Proof.
intros.
unfold Transitive.
unfold Func.equiv.
intros.
rewrite H, H0.
reflexivity.
Qed.
intros.
unfold Transitive.
unfold Func.equiv.
intros.
rewrite H, H0.
reflexivity.
Qed.
Lemma Sets_equiv_refl: ∀A, Reflexive (@Sets.equiv A).
Proof.
intros.
unfold Reflexive.
unfold Sets.equiv.
intros.
tauto.
Qed.
intros.
unfold Reflexive.
unfold Sets.equiv.
intros.
tauto.
Qed.
Lemma Sets_equiv_sym: ∀A, Symmetric (@Sets.equiv A).
Proof.
intros.
unfold Symmetric.
unfold Sets.equiv.
intros.
rewrite H.
reflexivity.
Qed.
intros.
unfold Symmetric.
unfold Sets.equiv.
intros.
rewrite H.
reflexivity.
Qed.
Lemma Sets_equiv_trans: ∀A, Transitive (@Sets.equiv A).
Proof.
intros.
unfold Transitive.
unfold Sets.equiv.
intros.
rewrite H, H0.
reflexivity.
Qed.
intros.
unfold Transitive.
unfold Sets.equiv.
intros.
rewrite H, H0.
reflexivity.
Qed.
Moreover, Func.equiv is preserved by Func.add, Func.sub and
   Func.mul. You may want to state a lemma as follows: 
Lemma Func_add_equiv_naive: ∀A (f1 f2 g1 g2: A -> Z),
Func.equiv f1 f2 ->
Func.equiv g1 g2 ->
Func.equiv (f1 + g1)%Func (f2 + g2)%Func.
Proof.
Abort.
Func.equiv f1 f2 ->
Func.equiv g1 g2 ->
Func.equiv (f1 + g1)%Func (f2 + g2)%Func.
Proof.
Abort.
Coq suggests you use Proper to describe such preservation theorems. 
Lemma Func_add_equiv: ∀A,
Proper (@Func.equiv A ==> @Func.equiv A ==> @Func.equiv A) Func.add.
Proof.
intros.
Proper (@Func.equiv A ==> @Func.equiv A ==> @Func.equiv A) Func.add.
Proof.
intros.
The following line exposes Proper's meaning. 
  unfold Proper, respectful.
intros f1 f2 ? g1 g2 ?.
unfold Func.equiv in H.
unfold Func.equiv in H0.
unfold Func.equiv.
intros.
unfold Func.add.
rewrite H, H0.
reflexivity.
Qed.
Lemma Func_sub_equiv: ∀A,
Proper (@Func.equiv A ==> @Func.equiv A ==> @Func.equiv A) Func.sub.
Lemma Func_mul_equiv: ∀A,
Proper (@Func.equiv A ==> @Func.equiv A ==> @Func.equiv A) Func.mul.
intros f1 f2 ? g1 g2 ?.
unfold Func.equiv in H.
unfold Func.equiv in H0.
unfold Func.equiv.
intros.
unfold Func.add.
rewrite H, H0.
reflexivity.
Qed.
Lemma Func_sub_equiv: ∀A,
Proper (@Func.equiv A ==> @Func.equiv A ==> @Func.equiv A) Func.sub.
Proof.
intros.
unfold Proper, respectful.
intros f1 f2 ? g1 g2 ?.
unfold Func.equiv in H.
unfold Func.equiv in H0.
unfold Func.equiv.
intros.
unfold Func.sub.
rewrite H, H0.
reflexivity.
Qed.
intros.
unfold Proper, respectful.
intros f1 f2 ? g1 g2 ?.
unfold Func.equiv in H.
unfold Func.equiv in H0.
unfold Func.equiv.
intros.
unfold Func.sub.
rewrite H, H0.
reflexivity.
Qed.
Lemma Func_mul_equiv: ∀A,
Proper (@Func.equiv A ==> @Func.equiv A ==> @Func.equiv A) Func.mul.
Proof.
intros.
unfold Proper, respectful.
intros f1 f2 ? g1 g2 ?.
unfold Func.equiv in H.
unfold Func.equiv in H0.
unfold Func.equiv.
intros.
unfold Func.mul.
rewrite H, H0.
reflexivity.
Qed.
intros.
unfold Proper, respectful.
intros f1 f2 ? g1 g2 ?.
unfold Func.equiv in H.
unfold Func.equiv in H0.
unfold Func.equiv.
intros.
unfold Func.mul.
rewrite H, H0.
reflexivity.
Qed.
Why using Proper is better than Func_add_equiv_naive? Coq supports
    rewriting via Proper! 
Existing Instances Func_equiv_refl
Func_equiv_sym
Func_equiv_trans
Func_add_equiv
Func_sub_equiv
Func_mul_equiv.
Fact domo_of_rewrite: ∀f g h j l: Z -> Z,
Func.equiv f (j + l)%Func ->
Func.equiv g h ->
Func.equiv (g + f)%Func (j + l + h)%Func.
Proof.
intros.
rewrite (Func_add_comm g f).
rewrite H.
rewrite H0.
reflexivity.
Qed.
Func_equiv_sym
Func_equiv_trans
Func_add_equiv
Func_sub_equiv
Func_mul_equiv.
Fact domo_of_rewrite: ∀f g h j l: Z -> Z,
Func.equiv f (j + l)%Func ->
Func.equiv g h ->
Func.equiv (g + f)%Func (j + l + h)%Func.
Proof.
intros.
rewrite (Func_add_comm g f).
rewrite H.
rewrite H0.
reflexivity.
Qed.
The first rewrite is sound because Func.equiv is transitive. The second
    and third rewrite are sound because Func.equiv is preserved by
    Func.add and Func.equiv is reflexive. Coq reasons about that
    automatically according to the registrations of Existing Instances. 
 
 Again, do not drown in Coq's details! But try to understand higher-order
    functions and their higher-order properties. This example shows how higher
    order objects help describing nontrivial theories in an abstract and
    intuitive way. 
 
 
 We will use higher-order objects to define boolean expressions' denotations.
    Before that, we need more definitions about sets. 
Evaluating Boolean Expressions
Module Sets2.
Module Sets.
Definition full {A: Type}: A -> Prop := fun _ ⇒ True.
Definition empty {A: Type}: A -> Prop := fun _ ⇒ False.
Definition intersect {A: Type} (X Y: A -> Prop) := fun a ⇒ X a ∧ Y a.
Definition complement {A: Type} (X: A -> Prop) := fun a ⇒ ¬X a.
End Sets.
End Sets2.
Import Sets2.
Module Sets.
Definition full {A: Type}: A -> Prop := fun _ ⇒ True.
Definition empty {A: Type}: A -> Prop := fun _ ⇒ False.
Definition intersect {A: Type} (X Y: A -> Prop) := fun a ⇒ X a ∧ Y a.
Definition complement {A: Type} (X: A -> Prop) := fun a ⇒ ¬X a.
End Sets.
End Sets2.
Import Sets2.
Recall that our syntax trees of boolean expressions are:
    b ::= true
        | false
        | a == a
        | a <= a
        | ! b
        | b && b
 
Fixpoint beval (b : bexp) : state -> Prop :=
match b with
| BTrue ⇒ Sets.full
| BFalse ⇒ Sets.empty
| BEq a1 a2 ⇒ Func.test_eq (aeval a1) (aeval a2)
| BLe a1 a2 ⇒ Func.test_le (aeval a1) (aeval a2)
| BNot b1 ⇒ Sets.complement (beval b1)
| BAnd b1 b2 ⇒ Sets.intersect (beval b1) (beval b2)
end.
match b with
| BTrue ⇒ Sets.full
| BFalse ⇒ Sets.empty
| BEq a1 a2 ⇒ Func.test_eq (aeval a1) (aeval a2)
| BLe a1 a2 ⇒ Func.test_le (aeval a1) (aeval a2)
| BNot b1 ⇒ Sets.complement (beval b1)
| BAnd b1 b2 ⇒ Sets.intersect (beval b1) (beval b2)
end.
Evaluating Command
Module BinRel.
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 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 test_rel {A: Type} (X: A -> Prop): A -> A -> Prop :=
fun x y ⇒ x = y ∧ X x.
End BinRel.
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 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 test_rel {A: Type} (X: A -> Prop): A -> A -> Prop :=
fun x y ⇒ x = y ∧ X x.
End BinRel.
Using these basic definitions about relation, we can easily define the
    denotation of empty commands, assignment commands, sequential composition
    and if-then-else commands. 
Module CEval_first_try.
Definition if_sem
(b: bexp)
(then_branch else_branch: state -> state -> Prop)
: state -> state -> Prop
:=
BinRel.union
(BinRel.concat (BinRel.test_rel (beval b)) then_branch)
(BinRel.concat (BinRel.test_rel (beval (BNot b))) else_branch).
Fixpoint ceval (c: com): state -> state -> Prop :=
match c with
| CSkip ⇒ BinRel.id
| CAss X E ⇒
fun st1 st2 ⇒
st2 X = aeval E st1 ∧
∀Y, X ≠ Y -> st1 Y = st2 Y
| CSeq c1 c2 ⇒ BinRel.concat (ceval c1) (ceval c2)
| CIf b c1 c2 ⇒ if_sem b (ceval c1) (ceval c2)
| CWhile _ _ ⇒ BinRel.empty
end.
End CEval_first_try.
(* 2021-03-15 23:15 *)
Definition if_sem
(b: bexp)
(then_branch else_branch: state -> state -> Prop)
: state -> state -> Prop
:=
BinRel.union
(BinRel.concat (BinRel.test_rel (beval b)) then_branch)
(BinRel.concat (BinRel.test_rel (beval (BNot b))) else_branch).
Fixpoint ceval (c: com): state -> state -> Prop :=
match c with
| CSkip ⇒ BinRel.id
| CAss X E ⇒
fun st1 st2 ⇒
st2 X = aeval E st1 ∧
∀Y, X ≠ Y -> st1 Y = st2 Y
| CSeq c1 c2 ⇒ BinRel.concat (ceval c1) (ceval c2)
| CIf b c1 c2 ⇒ if_sem b (ceval c1) (ceval c2)
| CWhile _ _ ⇒ BinRel.empty
end.
End CEval_first_try.
(* 2021-03-15 23:15 *)