(** 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.
(* ################################################################# *)
(** * Review: Program Expression's Denotational Semantics *)
(** We have learnt how to define integer expression's denotational semantics.
We can define it by recursion. In this Coq description, [state] means the
set of program states and every program state is a function from program
variables to integer values, i.e. [var -> Z]. *)
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.
(** 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.
(** Which statements are correct?
- [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].
*)
(** Answer:
- Correct;
- Incorrect;
- Incorrect;
- Correct.
*)
(* ################################################################# *)
(** * Higher-Order Thinking *)
(** Let's take another look of our previous definition of [aeval]. What is its
Coq type? *)
Check aeval.
(* 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.
(** 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.
(** 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.
(** 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.
(** 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.
(** 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.
(* ================================================================= *)
(** ** Higher-order Properties *)
Module Func3.
Module Func.
Definition equiv {A: Type} (f g: A -> Z): Prop :=
forall a, f a = g a.
Definition le {A: Type} (f g: A -> Z): Prop :=
forall a, f a <= g a.
End Func.
End Func3.
Import Func3.
Module Sets1.
Module Sets.
Definition equiv {A: Type} (X Y: A -> Prop): Prop :=
forall a, X a <-> Y a.
End Sets.
End Sets1.
Import Sets1.
(** Here is a lemma about function equivalence. *)
Lemma Func_add_comm: forall {A} (f g: A -> Z),
Func.equiv (f + g)%Func (g + f)%Func.
Proof.
(* 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: forall A, Reflexive (@Func.equiv A).
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: forall A, Symmetric (@Func.equiv A).
Proof.
intros.
unfold Symmetric.
unfold Func.equiv.
intros.
rewrite H.
reflexivity.
Qed.
Lemma Func_equiv_trans: forall A, Transitive (@Func.equiv A).
Proof.
intros.
unfold Transitive.
unfold Func.equiv.
intros.
rewrite H, H0.
reflexivity.
Qed.
Lemma Sets_equiv_refl: forall A, Reflexive (@Sets.equiv A).
Proof.
intros.
unfold Reflexive.
unfold Sets.equiv.
intros.
tauto.
Qed.
Lemma Sets_equiv_sym: forall A, Symmetric (@Sets.equiv A).
Proof.
intros.
unfold Symmetric.
unfold Sets.equiv.
intros.
rewrite H.
reflexivity.
Qed.
Lemma Sets_equiv_trans: forall A, Transitive (@Sets.equiv A).
Proof.
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: forall 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.
(** Coq suggests you use [Proper] to describe such preservation theorems. *)
Lemma Func_add_equiv: forall A,
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: forall 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.
Lemma Func_mul_equiv: forall 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.
(** 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: forall 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. *)
(* ################################################################# *)
(** * Evaluating Boolean Expressions *)
(** We will use higher-order objects to define boolean expressions' denotations.
Before that, we need more definitions about sets. *)
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.
(** 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.
(* ################################################################# *)
(** * Evaluating Command *)
(** Next we need to define what it means to evaluate a command. One idea is to
define such evaluation as a function from beginning state and command to
ending state. But the fact that [WHILE] loops don't necessarily terminate
means that such evaluating function cannot be a total function; it must be a
partial function. Although such definition is no problem in theory, computer
scientists choose not to do this since it is less extensible. Also, if you
try do write it in Coq, it is nontrivial. *)
(** Usually, computer scientists use a set of state pairs [S] to represent a
program [c]'s denotation. Specifically, if a program state pair [(st1, st2)]
is an element of [S], then executing [c] from state [st1] may terminate with
state [st2]. In other words, the denotation of a program has type
[state -> state -> Prop] in Coq. Remark: this is different from Hoare
triples. Hoare triples are about assertion pairs but a program's denotation
is about program state pairs. *)
(** 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. It is like using [A -> Prop] to describe subsets of [A]. As a
preparation, we first define some basic concepts about relations. *)
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 => exists 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 /\
forall 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 *)