Lecture notes 20190319

First Order Logic 2

Remark. Some material in this lecture is from << Software Foundation >> volume 1 and volume 2.
Require Import PL.Imp5.

Falsehood and Negation

So far, we have mostly been concerned with proving that certain things are true — addition is commutative, appending lists is associative, etc. Of course, we may also be interested in negative results, showing that some given proposition is not true. In Coq, such statements are expressed with the negation operator ¬.
Coq defines ¬ P as P False, where False is a specific contradictory proposition defined in the standard library.
Module MyNot.

Definition not (P:Prop) := PFalse.

Notation "¬x" := (not x) : type_scope.

Check not.
(* ===> Prop -> Prop *)

End MyNot.

Theorem ex_falso_quodlibet : (P:Prop),
  FalseP.
Proof.
  (* WORKED IN CLASS *)
  intros P contra.
  destruct contra. Qed.
The Latin ex falso quodlibet means, literally, "from falsehood follows whatever you like"; this is another common name for the principle of explosion.
It takes a little practice to get used to working with negation in Coq. Even though you can see perfectly well why a statement involving negation is true, it can be a little tricky at first to get things into the right configuration so that Coq can understand it! Here are proofs of a few familiar facts to get you warmed up.
Theorem not_False :
  ¬False.
Proof.
  unfold not. intros H. destruct H. Qed.

Theorem contradiction_implies_anything : P Q : Prop,
  (P ∧ ¬P) → Q.
Proof.
  (* WORKED IN CLASS *)
  intros P Q [HP HNA]. unfold not in HNA.
  apply HNA in HP. destruct HP. Qed.

Theorem double_neg_intro : P : Prop,
  P → ~~P.
Proof.
  (* WORKED IN CLASS *)
  intros P H. unfold not. intros G. apply G. apply H. Qed.
The following theorem is one of four De Morgan laws.
Theorem or_not_and: P Q : Prop, ¬P ∨ ¬Q → ¬(PQ).
Proof.
(* WORKED IN CLASS *)
  intros.
  unfold not.
  intros.
  destruct H0.
  destruct H.
  + unfold not in H.
    apply H.
    exact H0.
  + unfold not in H.
    apply H.
    exact H1.
Qed.
Besides the fact that P and ¬P cannot be both true, one of them must be true. This principle is called law of excluded middle. In logic studies, this law distinguishes classical logic (经典逻辑) and intuitionistic logic (直觉主义逻辑). Thus, it is named classic in Coq.
Check classic.
  (* : forall P : Prop, P \/ ~ P *)

Theorem double_neg_elim : P : Prop,
  ~~PP.
Proof.
  intros.
  pose proof classic P.
  destruct H0.
  + exact H0.
  + unfold not at 1 in H.
    pose proof H H0.
    destruct H1.
Qed.

Theorem not_and_or: P Q : Prop, ¬(PQ) → ¬P ∨ ¬Q.
Proof.
  intros.
  pose proof classic P.
  destruct H0.
  + pose proof classic Q.
    destruct H1.
    - assert (PQ).
      {
        split.
        + exact H0.
        + exact H1.
      }
      unfold not in H.
      pose proof H H2.
      destruct H3.
    - right.
      exact H1.
  + left.
    exact H0.
Qed.

Existential Quantification

To say that there is some x such that some property P holds of x, we write x, P in Coq and write EXISTS x, P in our assertion language. To prove a statement of the form x, P, we must show that P holds for some specific choice of value for x, known as the witness of the existential. This is done in two steps: First, we explicitly tell Coq which witness t we have in mind by invoking the tactic t. Then we prove that P holds after all occurrences of x are replaced by t.
Lemma four_is_even : n, 4 = n + n.
Proof.
  2.
  omega.
Qed.

Lemma six_is_not_prime: n, 2 ≤ n < 6 ∧ q, n * q = 6.
Proof.
  2.
  split.
  + omega.
  + 3.
    omega.
Qed.
Conversely, if we have an existential hypothesis x, P in Coq, we can destruct it
Theorem exists_example : n,
  (m, n = 4 + m) →
  (o, n = 2 + o).
Proof.
(* WORKED IN CLASS *)
  intros.
  destruct H.
  (2 + x).
  omega.
Qed.

Theorem dist_exists_and : (X:Type) (P Q : XProp),
  (x, P xQ x) → (x, P x) ∧ (x, Q x).
Proof.
(* WORKED IN CLASS *)
  intros.
  destruct H as [x [HP HQ]].
  split.
  + x.
    exact HP.
  + x.
    exact HQ.
Qed.
Notice that the reverse direction of this theorem is not true. For example, there obviously exists at least one even number and one odd number but there is not number which is both even and odd.
Module sample_derivation.

Local Instance X: var := new_var().

Fact der: (m n: Z),
  0 ≤ m AND [[X]] == m
  EXISTS q, n * q + [[X]] == m AND 0 ≤ [[X]].
Proof.
(* WORKED IN CLASS *)
  intros.
  entailer.
  intros.
  0.
  omega.
Qed.

End sample_derivation.

Universal Quantification

The Coq tactic support for universal quantification is very similar to implication's. In short, we use pose proof (or specialize) for universal quantification in assumptions and we use intros for universal quantification in a conclusion. Also, apply can be used for writing backward proofs.
Lemma forall_comm: (P: ZZProp),
  (x y, P x y) →
  (y x, P x y).
Proof.
  intros.
  apply H.
Qed.

Lemma forall_example1: (P Q: ZZProp),
  P 0 1 →
  (x y, P x yQ x y) →
  Q 0 1.
Proof.
  intros.
  pose proof H0 0 1 H.
  exact H1.
Qed.

Lemma forall_example2: (P Q: ZZProp),
  P 0 1 →
  (x y, P x yQ x y) →
  Q 0 1.
Proof.
  intros.
  specialize (H0 0 1 H).
  exact H0.
Qed.

Lemma forall_example3: (P Q: ZZProp),
  P 0 1 →
  (x y, P x yQ x y) →
  Q 0 1.
Proof.
  intros.
  apply H0.
  exact H.
Qed.

Lemma forall_example4: (P Q: ZZProp),
  P 0 1 →
  (x y, P x yQ x y) →
  Q 0 1.
Proof.
  intros.
  apply H0 in H.
  exact H.
Qed.

Exercise: 1 star, standard (dist_not_exists)

Prove that "P holds for all x" implies "there is no x for which P does not hold." (Hint: destruct H as [x E] works on existential assumptions!)
Theorem dist_not_exists : (X:Type) (P : XProp),
  (x, P x) → ¬(x, ¬P x).
Proof.
(* FILL IN HERE *) Admitted.

First Order Logic for Assertion Derivation

The Imp library also supports first order logic proof directly, i.e., in order to verify assertion derivation, you can choose not to reduce it to normal Coq propositions. The following proof rules are provided for you.
Check AND_left1.
  (* : forall P Q R : Assertion,
       P ⊢ R -> P AND Q ⊢ R *)


Check AND_left2.
  (* : forall P Q R : Assertion,
       Q ⊢ R -> P AND Q ⊢ R *)


Check AND_right.
  (* : forall P Q R : Assertion,
       P ⊢ Q -> P ⊢ R -> P ⊢ Q AND R *)


Check OR_left.
  (* : forall P Q R : Assertion,
       P ⊢ R -> Q ⊢ R -> P OR Q ⊢ R *)


Check OR_right1.
  (* : forall P Q R : Assertion,
       P ⊢ Q -> P ⊢ Q OR R *)


Check OR_right2.
  (* : forall P Q R : Assertion,
       P ⊢ R -> P ⊢ Q OR R *)


Check CONTRA.
  (* : forall P Q : Assertion, P AND NOT P ⊢ Q *)

Check LEM.
  (* : forall P Q : Assertion, P ⊢ Q OR NOT Q *)

Check True_right.
  (* : forall P : Assertion, P ⊢ True *)

Check False_left.
  (* : forall P : Assertion, False ⊢ P *)

Check EXISTS_left.
  (* : forall P (Q : Assertion),
       (forall x : Z, P x ⊢ Q) ->
       EXISTS x, P x ⊢ Q *)


Check EXISTS_right.
  (* : forall (P : Assertion) Q (x : Z),
       P ⊢ Q x -> P ⊢ EXISTS x, Q x *)


Check FORALL_left.
  (* : forall P (Q : Assertion) (x : Z),
       P x ⊢ Q -> FORALL x0, P x0 ⊢ Q *)


Check FORALL_right.
  (* : forall (P : Assertion) Q,
       (forall x : Z, P ⊢ Q x) ->
       P ⊢ FORALL x, Q x *)


Check derives_refl.
  (* : forall P : Assertion, P ⊢ P *)

Check derives_trans.
  (* : forall P Q R : Assertion,
       P ⊢ Q -> Q ⊢ R -> P ⊢ R *)


Module min_if.
Import Axiomatic_semantics.

Local Instance A: var := new_var().
Local Instance B: var := new_var().
Local Instance C: var := new_var().
Here is the decorated program for min_if:
  /* [[A]] == a AND [[B]] == b */
  If A ≤ B
  Then
    /* [[A]] == a AND [[B]] == b AND [[A ≤ B]] */
    /* [[A]] == a AND a ≤ b */
    C ::= A
    /* EXISTS c[[A]] == a AND a ≤ b AND [[C]] == [[A]] */
    /* [[A]] == a AND a ≤ b AND [[C]] == [[A]] */
    /* [[C]] == a AND a ≤ b OR [[C]] == b AND b < a */
  Else
    /* [[A]] == a AND [[B]] == b AND NOT [[A ≤ B]] */
    /* [[B]] == b AND b < a */
    C ::= B
    /* EXISTS c[[B]] == b AND b < a AND [[C]] == [[B]] */
    /* [[B]] == b AND b < a AND [[C]] == [[B]] */
    /* [[C]] == a AND a ≤ b OR [[C]] == b AND b < a */
  EndIf
  /* [[C]] == a AND a ≤ b OR [[C]] == b AND b < a */.
And here is part of derivation needed in proof:
Fact der1_2: a b: Z,
  [[A]] == a AND ab AND [[C]] == [[A]] ⊢
  [[C]] == a AND ab OR [[C]] == b AND b < a.
Proof.
  intros.
As mentioned before, we can use entailer to reduce this proof goal.
  entailer.
  intros.
  omega.
Qed.

Fact der1_2': a b: Z,
  [[A]] == a AND ab AND [[C]] == [[A]] ⊢
  [[C]] == a AND ab OR [[C]] == b AND b < a.
Proof.
  intros.
But we can also choose to apply some first order logic proof before calling entailer.
  apply OR_right1.
  entailer.
  intros.
  omega.
Qed.

Fact der1_2'': a b: Z,
  [[A]] == a AND ab AND [[C]] == [[A]] ⊢
  [[C]] == a AND ab OR [[C]] == b AND b < a.
Proof.
  intros.
  apply OR_right1.
  apply AND_right.
  + entailer.
    intros.
    omega.
  + apply AND_left1.
    apply AND_left2.
    apply derives_refl.
Qed.

End min_if.

Module slow_div.

Local Instance X: var := new_var().
Local Instance Y: var := new_var().

Lemma derivation1: m n: Z,
  0 ≤ m AND [[X]] == m AND [[Y]] == 0 ⊢
  n * [[Y]] + [[X]] == m AND 0 ≤ [[X]].
Proof.
  intros.
  entailer.
  intros.
  destruct H.
  rewrite H0.
  omega.
Qed.

Lemma derivation2: m n: Z,
  EXISTS z, n * [[Y]] + z == m AND 0 ≤ z AND nz AND [[X]] == z - n
  n * [[Y]] + [[X]] + n == m AND 0 ≤ [[X]].
Proof.
  intros.
  apply EXISTS_left.
  intros.
  entailer.
  intros.
  omega.
Qed.

Lemma derivation3: m n: Z,
  EXISTS z, n * z + [[X]] + n == m AND 0 ≤ [[X]] AND [[Y]] == z + 1 ⊢
  n * [[Y]] + [[X]] == m AND 0 ≤ [[X]].
Proof.
  intros.
  entailer.
  intros.
  destruct H as [k [[? ?] ?]].
  split.
  + rewrite H1.
    Search (_ * (_ + _)).
    rewrite Z.mul_add_distr_l.
    omega.
  + exact H0.
Qed.

Lemma derivation4: m n: Z,
  n * [[Y]] + [[X]] == m AND 0 ≤ [[X]] AND NOT [[nX]] ⊢
  n * [[Y]] + [[X]] == m AND 0 ≤ [[X]] AND [[X]] < n.
Proof.
  intros.
  apply AND_right.
  + apply AND_left1.
    apply derives_refl.
  + apply AND_left2.
    entailer.
    intros.
    omega.
Qed.

End slow_div.

Derived Proof Rules

Previously, when we want to strengthen preconditions or weaken postconditions in proof goals, we do that in pairs by applying hoare_consequence in Coq. But when we only want to strengthen a precondition, it is inconvenient for writing formal proofs in Coq. We are forced to prove that the postcondition implies itself. In that situation, we hope we can use the following proof rule instead.
Module derived_rules.
Import Axiomatic_semantics.

Corollary hoare_consequence_pre: P P' Q c,
  PP'
  {{ P' }} c {{ Q }} →
  {{ P }} c {{ Q }}.
Now, we can derive this rule from hoare_consequence and derives_refl.
Proof.
  intros.
  eapply hoare_consequence.
  + exact H.
  + exact H0.
  + apply derives_refl.
Qed.
Similarly, we have a single-sided consequence rule for postconditions as well.
Corollary hoare_consequence_post: P Q Q' c,
  {{ P }} c {{ Q' }} →
  Q'Q
  {{ P }} c {{ Q }}.
Proof.
  intros.
  eapply hoare_consequence.
  + apply derives_refl.
  + exact H.
  + exact H0.
Qed.
Since we prove these two rules from primary rules, we call them derived rules (导出规则).
End derived_rules.

Denotational Semantics at the First Glance, Expression Evaluation


Module Denotational_semantics.
We have seen how a Hoare logic (axiomatic semantics) defines the relation between the possible programs state sets before and after executing a program. It is convenient for describing useful program specifications and verifying them. However, it is not obvious how this semantic definition connects with compiler implementation.
Denotational semantics (指称语义) is another style of semantic definition. It directly defines program behavior on concrete program state, but as a trade-off, it does not directly connect with useful program specifications.
Today we will use integer expression evaluation to illustrate the difference. Remember that the integer expressions of our simple imperative language have the following grammar:
    a ::= Z
        | var
        | a + a
        | a - a
        | a * a
In Hoare logic, we talk about the property that an expressions a's value should satisfy. In denotational semantics, we directly define expressions' evaluation result.
The following definition says, for a fixed program state st, the value of expression a is defined recursively on its syntax tree.
Fixpoint aeval (st : state) (a : aexp) : Z :=
  match a with
  | ANum nn
  | AId Xst X
  | 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.
Here
  • The keyword Fixpoint means that we defines a recursive function.
  • The function aeval defines the evaluation result of integer expressions in our simple imperative programming language.
  • The argument announcement st: state says that st is a program state. Here, a program state is defined as a function from program variables to integers.
  • The argument announcement a: aexp says that a is an integer expression. We follow <<Software Foundation>>'s tradition to call integer expressions aexp.
  • The match expression (Coq expression) defines how this expression (program expression) evaluation is defined for different kind of a.
  • ANum n says if a is a constant expression n.
  • AId X says if a is a singleton variable X.
  • st X says: applying function st on X. Remember, st is a function from program variables to integers.
We can use similar approaches to define the length of an integer expression.
Fixpoint alength (a : aexp) : Z :=
  match a with
  | ANum n ⇒ 1
  | AId X ⇒ 1
  | APlus a1 a2 ⇒ (alength a1) + (alength a2) + 1
  | AMinus a1 a2 ⇒ (alength a1) + (alength a2) + 1
  | AMult a1 a2 ⇒ (alength a1) + (alength a2) + 1
  end.
And the following Coq function defines boolean expressions' evaluation:
Fixpoint beval (st : state) (b : bexp) : Prop :=
  match b with
  | BTrueTrue
  | BFalseFalse
  | BEq a1 a2 ⇒ (aeval st a1) = (aeval st a2)
  | BLe a1 a2 ⇒ (aeval st a1) ≤ (aeval st a2)
  | BNot b1 ⇒ ¬(beval st b1)
  | BAnd b1 b2 ⇒ (beval st b1) ∧ (beval st b2)
  end.

End Denotational_semantics.

(* Mon Mar 18 16:03:47 UTC 2019 *)