Lecture notes 20190412

Small Step Semantics 1

Remark. Some material in this lecture is from << Software Foundation >> volume 1 and volume 2.
Require Import PL.Imp6.
Require Coq.Relations.Relation_Operators.
Require Coq.Relations.Relation_Definitions.

General Idea: Small Step

Till now, we have learnt how to define a programming language's denotational semantics. Most commonly, the denotation of a program is defined as a binary relation between program states. Such denotational semantics are also called big-step operational semantics — the denotation of a program tells the result of program execution (given the starting state), "all in one step".
Small step semantics, on the other hand, talks more about intermediate states. It defines how a program will execute, step by step. If we use the following expression's evaluation process as example,
    2 + 2 + 3 * 4,
denotational semantics says:
    2 + 2 + 3 * 4 ==> 16
while small step operational semantics says:
      2 + 2 + 3 * 4
      --> 4 + 3 * 4
      --> 4 + 12
      --> 16.
In short, we will learn how to define a "small-step" relation that specifies, for a given program, how the "atomic steps" of computation are performed.

Small Step Semantics for Expression Evaluation

It can be useful to think of this step relation of expression evaluation as an abstract machine.
  • At any moment, the state of the machine is an integer expression.
  • A step of this machine is an atomic unit of computation — here, a single arithmetic operation or loading program variable's value.
  • The halting states of the machine are ones where there is no more computation to be done.
Given an expression a, we can compute its value as follows:
  • Take a as the starting state of the machine.
  • Repeatedly use the step relation to find a sequence of machine states, starting with a, where each state steps to the next.
  • When no more forward step is possible, "read out" the final state of the machine as the result of computation.
Intuitively, it is clear that the final states of the machine are always constant expressions Anum n for some n.
Inductive aexp_halt: aexpProp :=
  | AH_num : n, aexp_halt (ANum n).
Of course, we could define it using a Coq function instead of an inductive predicate. Here is this alternative (but equivalent approach) approach.
Module Playground.

Definition aexp_halt (a: aexp): Prop :=
  match a with
  | ANum _True
  | _False
  end.

End Playground.
Then we define our step relation.
Inductive astep : stateaexpaexpProp :=
  | AS_Id : st X,
      astep st
        (AId X) (ANum (st X))

  | AS_Plus1 : st a1 a1' a2,
      astep st
        a1 a1'
      astep st
        (APlus a1 a2) (APlus a1' a2)
  | AS_Plus2 : st a1 a2 a2',
      aexp_halt a1
      astep st
        a2 a2'
      astep st
        (APlus a1 a2) (APlus a1 a2')
  | AS_Plus : st n1 n2,
      astep st
        (APlus (ANum n1) (ANum n2)) (ANum (n1 + n2))

  | AS_Minus1 : st a1 a1' a2,
      astep st
        a1 a1'
      astep st
        (AMinus a1 a2) (AMinus a1' a2)
  | AS_Minus2 : st a1 a2 a2',
      aexp_halt a1
      astep st
        a2 a2'
      astep st
        (AMinus a1 a2) (AMinus a1 a2')
  | AS_Minus : st n1 n2,
      astep st
        (AMinus (ANum n1) (ANum n2)) (ANum (n1 - n2))

  | AS_Mult1 : st a1 a1' a2,
      astep st
        a1 a1'
      astep st
        (AMult a1 a2) (AMult a1' a2)
  | AS_Mult2 : st a1 a2 a2',
      aexp_halt a1
      astep st
        a2 a2'
      astep st
        (AMult a1 a2) (AMult a1 a2')
  | AS_Mult : st n1 n2,
      astep st
        (AMult (ANum n1) (ANum n2)) (ANum (n1 * n2)).
This definition seems to be super long. Let's read it part by part. But please keep in mind that what we define here is only a single step evaluation relation.
The first part of this definition talks about the value of a program variable:
      astep st
        (AId X) (ANum (st X)).
In short, it says: a program variable X's variable is X's variable and this evaluation process has only one step.
The second part of this definition talks about how the sum of two subexpressions are computed.
  | AS_Plus1 : st a1 a1' a2,
      astep st
        a1 a1' →
      astep st
        (APlus a1 a2) (APlus a1' a2)
  | AS_Plus2 : st a1 a2 a2',
      aexp_halt a1 →
      astep st
        a2 a2' →
      astep st
        (APlus a1 a2) (APlus a1 a2')
  | AS_Plus : st n1 n2,
      astep st
        (APlus (ANum n1) (ANum n2)) (ANum (n1 + n2))
It says, the left side is computed first, then the right side. When both sides are computed, the sum of them can be computed in another step.
Combining these two parts together, we are already able to describe the evaluation process of some nontrivial examples. For example, when X's value is 1 and Y's value is 2, X + (3 + Y) will be evaluated by the following steps:
    X + (3 + Y)
    --> 1 + (3 + Y)
    --> 1 + (3 + 2)
    --> 1 + 5
    --> 6.
We can prove this in Coq.
Module Step_Example1.

Import Abstract_Pretty_Printing.

Example step_1: (X Y: var) (st: state),
  st X = 1 →
  astep st (X + (3 + Y)) (1 + (3 +Y)).
Proof.
  intros.
  apply AS_Plus1.
  rewrite <- H.
  apply AS_Id.
Qed.

Example step_2: (Y: var) (st: state),
  st Y = 2 →
  astep st (1 + (3 +Y)) (1 + (3 + 2)).
Proof.
  intros.
  apply AS_Plus2.
  { apply AH_num. }
  apply AS_Plus2.
  { apply AH_num. }
  rewrite <- H.
  apply AS_Id.
Qed.

Example step_3: (st: state),
  astep st (1 + (3 + 2)) (1 + 5).
Proof.
  intros.
  apply AS_Plus2.
  { apply AH_num. }
  apply AS_Plus.
Qed.

Example step_4: (st: state),
  astep st (1 + 5) 6.
Proof.
  intros.
  apply AS_Plus.
Qed.

End Step_Example1.
The small step semantics for "minus" and "multiplication" are defined in very similar way. And we can also define bool expression's evaluation as follows. If you forget details about bexp's inductive definition, just use Print bexp as a cheat sheet.
(* Print bexp. *)

Inductive bexp_halt: bexpProp :=
  | BH_True : bexp_halt BTrue
  | BH_False : bexp_halt BFalse.

Inductive bstep : statebexpbexpProp :=

  | BS_Eq1 : st a1 a1' a2,
      astep st
        a1 a1'
      bstep st
        (BEq a1 a2) (BEq a1' a2)
  | BS_Eq2 : st a1 a2 a2',
      aexp_halt a1
      astep st
        a2 a2'
      bstep st
        (BEq a1 a2) (BEq a1 a2')
  | BS_Eq_True : st n1 n2,
      n1 = n2
      bstep st
        (BEq (ANum n1) (ANum n2)) BTrue
  | BS_Eq_False : st n1 n2,
      n1n2
      bstep st
        (BEq (ANum n1) (ANum n2)) BFalse


  | BS_Le1 : st a1 a1' a2,
      astep st
        a1 a1'
      bstep st
        (BLe a1 a2) (BLe a1' a2)
  | BS_Le2 : st a1 a2 a2',
      aexp_halt a1
      astep st
        a2 a2'
      bstep st
        (BLe a1 a2) (BLe a1 a2')
  | BS_Le_True : st n1 n2,
      n1n2
      bstep st
        (BLe (ANum n1) (ANum n2)) BTrue
  | BS_Le_False : st n1 n2,
      n1 > n2
      bstep st
        (BLe (ANum n1) (ANum n2)) BFalse

  | BS_NotStep : st b1 b1',
      bstep st
        b1 b1'
      bstep st
        (BNot b1) (BNot b1')
  | BS_NotTrue : st,
      bstep st
        (BNot BTrue) BFalse
  | BS_NotFalse : st,
      bstep st
        (BNot BTrue) BTrue

  | BS_AndStep : st b1 b1' b2,
      bstep st
        b1 b1'
      bstep st
       (BAnd b1 b2) (BAnd b1' b2)
  | BS_AndTrue : st b,
      bstep st
       (BAnd BTrue b) b
  | BS_AndFalse : st b,
      bstep st
       (BAnd BFalse b) BFalse.
Remark: when evaluating a conjunction of two boolean expression, we use short circuit evaluation. That is, the right hand side will not be evaluated if the left hand side is false. For example, when X's value is 1, X 0 && 0 < X + 10 will be evaluated by the following steps:
    X ≤ 0 && 0 ≤ X + 10
    --> 1 ≤ 0 && 0 ≤ X + 10
    --> False && 0 ≤ X + 10
    --> False.
Module Step_Example2.

Import Abstract_Pretty_Printing.

Example step_1: (X: var) (st: state),
  st X = 1 →
  bstep st ((X ≤ 0) && (0 ≤ X + 10)) ((1 ≤ 0) && (0 ≤ X + 10)).
Proof.
  intros.
  apply BS_AndStep.
  apply BS_Le1.
  rewrite <- H.
  apply AS_Id.
Qed.

Example step_2: (X: var) (st: state),
  bstep st ((1 ≤ 0) && (0 ≤ X + 10)) (BFalse && (0 ≤ X + 10)).
Proof.
  intros.
  apply BS_AndStep.
  apply BS_Le_False.
  omega.
Qed.

Example step_3: (X: var) (st: state),
  bstep st (BFalse && (0 ≤ X + 10)) BFalse.
Proof.
  intros.
  apply BS_AndFalse.
Qed.

End Step_Example2.

Reflexive, Transive Closure

After our single step relation is defined, we can derive the multi-step relation based on it. That is we want to say: on state st, from expression a1 we can arrive a2 after some number of steps. More specifically, it can be zero step, one step, two steps, etc. In math, this multi-step relation is called the reflexive, transitive closure (自反传递闭包) of step.
The reflexive, transitive closure of a relation R is defined as:
  • (Def A1) the smallest relation that contains R and that is both reflexive and transitive.
It is equivalent to say:
  • (Def A2) the result of expanding R by reflexivity and transitivity;
  • (Def B1) the smallest reflexive relation which is closed under right-concatenating R;
  • (Def B2) the result of expanding the identity relation by right-concatenating R repeatedly;
  • (Def C1) the smallest reflexive relation which is closed under left-concatenating R;
  • (Def C2) the result of expanding the identity relation by left-concatenating R repeatedly;
  • (Def D) the union of the following relations: the identity relation, the relation R, the concatenation of two Rs, the concatenation of three R's, etc.
Let's formally describe some concepts in these equivalent definitions.
We have seen the first three definitions in our lectures about denotational semantics.
Module Relation_Definitions.
Identity relation.
Definition id {A: Type}: AAProp :=
  fun a ba = b.
Concatenation of two relations.
Definition concat {A B C: Type} (R1: ABProp) (R2: BCProp): ACProp:=
  fun a cb, R1 a bR2 b c.
The union of countably many relations.
Definition omega_union {A B: Type} (Rs: natABProp): ABProp :=
  fun a bn, Rs n a b.
Then we have some new definitions. Reflexivity is a property of relations.
Definition Reflexive {A: Type} (R: AAProp): Prop :=
  x, R x x.
Transitivity is another property of relations.
Definition Transitive {A: Type} (R: AAProp): Prop :=
  x y z, R x yR y zR x z.
We say that a relation R1 is a subrelation of R2 if every pair of elements in R1 is in R2. In some sense, subrelation can be treated as a property of pairs of relations.
Definition subrelation {A B: Type} (R R': ABProp): Prop:=
  (x : A) (y : B), R x yR' x y.
Furthermore, for any property Pr of relations, when we say "R is the smallest relation satisfying Pr", we mean "R satisfies Pr and for any other R', if R' satisfies Pr, then R is a subrelation of R'".
Definition is_smallest_relation {A B: Type} (Pr: (ABProp) → Prop) (R: ABProp) :=
  Pr RR', Pr R'subrelation R R'.

End Relation_Definitions.
Now, we are ready to formulate those equivalent definitions of reflexive, transitive closure.
Module A1.

Import Relation_Definitions.
The following definition says: Rc is R's reflexive, transitive closure if and only if it is the smallest relation that contains R and that is both reflexive and transitive.
Definition is_clos_refl_trans {A: Type} (R Rc: AAProp): Prop :=
  is_smallest_relation
    (fun Rc'subrelation R Rc'
                Reflexive Rc'
                Transitive Rc')
    Rc.
This definition does not say that any relation has a reflexive, transitive closure. We have to prove it later.
End A1.

Module A2.

Import Relation_Definitions.
The following definition says: Rc is R's reflexive, transitive closure if and only if it is the result of expanding R by reflexivity and transitivity. Here, expansion can be defined by Coq's inductive predicate.
Inductive clos_refl_trans {A: Type} (R: AAProp) : AAProp :=
    | rt_step x y (H : R x y) : clos_refl_trans R x y
    | rt_refl x : clos_refl_trans R x x
    | rt_trans x y z
          (Hxy : clos_refl_trans R x y)
          (Hyz : clos_refl_trans R y z) :
          clos_refl_trans R x z.
In short, for any relation R, we define clos_refl_trans R to be its reflexive, transitive closure. Now, let's read this Coq definition line-by-line and make sure that we understand it.
    rt_step x y (H : R x y) : clos_refl_trans R x y
This first constructor rt_step says: we start from R to defined clos_refl_trans R, i.e. for any x and y, clos_refl_trans R x y holds if R x y is true.
    rt_refl x : clos_refl_trans R x x
This second constructor rt_refl says: we expand clos_refl_trans R by reflexivity.
    rt_trans x y z
          (Hxy : clos_refl_trans R x y)
          (Hyz : clos_refl_trans R y z) :
          clos_refl_trans R x z.
And most interestingly, we expand clos_refl_trans R x y by transitivity using this constructor rt_trans. The intuition is that we keep adding new pairs (x, z) into the expansion result until no more updates can be made.
Module Example.

Local Open Scope nat.
For example, using this definition, we can prove that the reflexive and transitive closure of the next_nat relation coincides with the le relation.
Inductive next_nat (n : nat) : natProp :=
           nn : next_nat n (S n).

Theorem next_nat_closure_is_le : n m: nat,
  (nm) ↔ ((clos_refl_trans next_nat) n m).
Proof.
  intros n m. split.
  - (* -> *)
    intro H. induction H.
    + (* le_n *) apply rt_refl.
    + (* le_S *)
      apply rt_trans with m. apply IHle. apply rt_step.
      apply nn.
  - (* <- *)
    intro H. induction H.
    + (* rt_step *) inversion H. apply le_S. apply le_n.
    + (* rt_refl *) apply le_n.
    + (* rt_trans *)
      apply le_trans with y.
      apply IHclos_refl_trans1.
      apply IHclos_refl_trans2. Qed.

End Example.

End A2.

Module A1_vs_A2.

Import Relation_Definitions.
We mentioned that definition A1 and definition A2 are equivalent. Now, let's prove their equivalence. That is, we will show that the reflexive, transitive closure defined by A2 does satisfy the criterion defined by A1.
Theorem def_equiv: (A: Type) (R: AAProp),
  A1.is_clos_refl_trans R (A2.clos_refl_trans R).
Proof.
  intros.
  unfold A1.is_clos_refl_trans.
  unfold is_smallest_relation.
  split.
We first prove that A2.clos_refl_trans R is actually reflexive and transitive, and it does contain R. We then prove that it is the smallest relation that satisfies all these three properties.
  + assert (subrelation R (A2.clos_refl_trans R)).
    {
      unfold subrelation.
      intros.
      apply A2.rt_step.
      exact H.
    }
    assert (Reflexive (A2.clos_refl_trans R)).
    {
      unfold Reflexive.
      intros.
      apply A2.rt_refl.
    }
    assert (Transitive (A2.clos_refl_trans R)).
    {
      unfold Transitive.
      intros.
      apply A2.rt_trans with y.
      + exact H1.
      + exact H2.
    }
    tauto.
  + intros.
    destruct H as [? [? ?]].
Now, we suppose that R' is a relation that contains R and is both reflexive and transitive. We are going to prove that it is larger than A2.clos_refl_trans R.
    unfold subrelation.
    intros.
    induction H2.
    - (* rt_step case *)
      unfold subrelation in H.
      specialize (H x y H2).
      exact H.
    - (* rt_refl case *)
      unfold Reflexive in H0.
      apply H0.
    - (* rt_trans case *)
      unfold Transitive in H1.
      specialize (H1 x y z).
      tauto.
Qed.

End A1_vs_A2.

(* Among these two definitions, A2 is obviously easier to use in Coq. This
definition is also part of Coq's standard library. *)


Import Coq.Relations.Relation_Operators.
Import Coq.Relations.Relation_Definitions.

Print clos_refl_trans.
(* Inductive clos_refl_trans (A : Type) (R : relation A) (x : A) : A -> Prop :=
       rt_step : forall y : A, R x y -> clos_refl_trans A R x y
     | rt_refl : clos_refl_trans A R x x
     | rt_trans : forall y z : A,
                  clos_refl_trans A R x y ->
                  clos_refl_trans A R y z ->
                  clos_refl_trans A R x z *)


Print relation.
(* relation = fun A : Type => A -> A -> Prop
     : Type -> Type *)

In the beginning of this part, we also mentioned other definitions of reflexive, transitive closure. Definition B2 and C2 are also included in Coq standard library.
Print clos_refl_trans_n1.

(* Inductive clos_refl_trans_n1 (A : Type) (R : relation A) (x : A) : A -> Prop :=
       rtn1_refl : clos_refl_trans_n1 A R x x
     | rtn1_trans : forall y z : A,
                    R y z ->
                    clos_refl_trans_n1 A R x y ->
                    clos_refl_trans_n1 A R x z *)


Print clos_refl_trans_1n.
(* Inductive clos_refl_trans_1n (A : Type) (R : relation A) (x : A) : A -> Prop :=
       rt1n_refl : clos_refl_trans_1n A R x x
     | rt1n_trans : forall y z : A,
                    R x y ->
                    clos_refl_trans_1n A R y z ->
                    clos_refl_trans_1n A R x z *)

All these definitions are proved equivalent with each other in the standard library. You, students of the course, can try to write down other definitions (B1, C1 and D) and understand their equivalence.
(* Thu Apr 11 14:39:22 UTC 2019 *)