For reference, here is the big-step semantics for IMP. The
relations we define are: => i, meaning expression e
evaluates to the integer i under store s, and => s'
meaning command c, when run in store s, produces store s'.
(In class, I used \downarrow instead of =>.)
These relations are defined using the following inference rules:
(X1) * => i
=> i1 => i2 i1 b i2 = i
(X2) -------------------------------------------
=> i
(X3) => s(x)
(B1) => s
=> i
(B2) ---------------------
=> s[x|->i]
=> s1 => s'
(B3) ---------------------------
=> s'
=> i (i != 0) => s'
(B4) --------------------------------------
=> s'
=> 0 => s'
(B5) --------------------------------------
=> s'
=> s'
(B6) -----------------------------------------------
=> s'
Since we took the small-step semantics as the definition of what
it means for an IMP program to evaluate, we are obligated to prove
that the large-step semantics is an appropriate model for the
small-step semantics. That is, we are obligated to prove that:
1. => s' implies ->*
2. ->* s' implies => s'
Let's prove the first of these.
Theorem: => s' implies ->*
Proof: By induction on the height of the derivation D allowing us to
conclude => s'.
Base case: D is of height 0. Then D must be an instance of an axiom.
Thus, D is an instance of (B1) meaning that c = skip and we have
=> s'. In zero steps, ->*
Induction hypothesis: For all derivations D of height <= n where
D concludes =>s', it is also the case that ->* .
Induction step: We will show the property is preserved for derivations
D of height n+1 allowing us to conclude =>s'. Such a derivation
must end with an instance of rules B2-B6 so we will address these by
cases.
D1
----------
case B2: D = => i
--------------------
=> s[x|->i]
Oops! We need some lemmas. In particular, we would like to know
Lemma 1: => i implies ->* and
->*
Assuming this is true then it follows that:
->* [from Lemma 1 applied to D1]
-> i]> [instance of C1]
D1 D2
------------ -------------
case B3: D = => s1 => s'
---------------------------
=> s'
From the induction hypothesis applied to D1, we know that ->*.
We also know from the hypothesis applied to D2 that ->*.
How can we use this to construct a proof that ->* ?
We seem to need another lemma similar to the one above:
Lemma 2: If ->n then for all c2, ->n .
Given this lemma, we have:
->* [by IH applied to D1 and Lemma 2]
-> [by instance of C3]
->* [by IH applied to D2]
D1 D2
---------- -----------
case B4: D = => i (i != 0) => s'
-----------------------------------
=> s'
By Lemma 1 applied to D1, we know that ->*
. We can use an instance of C5 together with
the fact that i != 0 to conclude -> .
Finally, we can use the induction hypothesis applied to D2 to
conclude ->* s'.
D1 D2
---------- -----------
case B5: D = => 0 => s'
-----------------------------------
=> s'
By Lemma 1 applied to D1 we know that ->*
and then using rule C6 we know this steps
to and then using the induction hypothesis applied to D2,
we know that ->* .
D'
-------------------------------------------------
case B6: D = => s'
------------------------------------------------
=> s'
-> [rule C8]
->* [IH D']
Note that we still have to discharge the two lemmas. A good exercise
for you to do. Another good exercise is to prove the other theorem.
=======================================================================
Here's another exercise: Let us prove that for all c and s, there is no s'
such that => s'.
Suppose not. Collect up all othe derivations that end with a
conclusion of the form =>s'. Pick the smallest
such derivation D. Then D must end with an instance of rule B6
so D looks like:
D1
-------------------------------------------------
=> s'
------------------------------------------------
=> s'
for some D1. Then D1 must be an instance of either B4 or B5.
Technically, we need to argue that D1 can't end with B5, but
that's pretty obvious here so I won't go through the motions.
Thus, the derivation D really looks like this:
D2 D3
--------- ---------------------
=>s1 =>s'
--------------------------------
<1,s> => 1 (1 != 0) => s'
-------------------------------------------------
=> s'
------------------------------------------------
=> s'
So D3 is a derivation with conclusion of the form =>s'.
But D3 is necessarily of smaller height than D, contradicting our
assumption regarding D's minimality.
=======================================================================
Thus far, we've seen two different kinds of operational models. The
big-step is generally easier to specify and work with, but it doesn't
scale to many language features as we'll see later on. I already
hinted that some features, such as exceptions, become very painful
in a large-step setting, and other features, such as fine-grained
interleaving (for modelling concurrent activity) don't work at all
in a big step setting. And of course, we can't say anything about
non-terminating expressions in the big-step setting. This is in large
part because our *meta-language* of inference rules is too weak to
accomodate this.
=======================================================================
Next, I want to demonstrate the ideas behind denotational semantics.
For the most part, it's going to look a lot like our large-step
semantics. The key difference is the meta-language. We're going
to use (mathematical) sets to describe the semantics of commands.
In essence, we're going to compile expressions e and commands c
directly into set theory. That will allow us to reason (at the level
of sets) about the equivalence of two expressions or two commands.
Recall that s ranges over Stores and that Stores are functions
from identifiers to integers:
s in Store = Id -> Z
We'll begin by giving a translation E[] for expressions with type:
E[] : Exp -> Store -> Z
and the definition will look like this:
E[i] s = i
E[x] s = s(x)
E[e1 b e2] s = (E[e1] s) b (E[e2] s)
Pretty simple, huh? This is just like writing an interpreter in
a functional language such as ML or Scheme. It's pretty easy to
prove that for all e and s, => i iff E[e] s = i. The big-step
semantics and the denotational semantics line right up.
Now let's move on to commands. The translation of a command will
be a function from stores to stores. For example, we ought to have
the property that C[skip] is equivalent to the identity function on
stores, and C[c1;c2] is something like function composition.
However, there's a problem. What function should we get out of
C[while 1 do skip]? Intuitively, we *don't* get a store out when
we run this command (in any store) because it runs forever. We
can model this in the set-theoretic setting by treating commands
as *partial functions* from stores to stores. That is, we can
think of the meaning of a command c as:
{(s,s') | |- => s'}
or equivalently:
{(s,s') | ->* }
With this kind of interpretation, C[while 1 do skip] will be the
empty set or the no-where defined partial function. Okay, so now
we know the type of C[]. We can write this as:
C[] : Com -> Store x Store
Let's start writing down the definition of C[]:
C[skip] = {(s,s)}
C[x:=e] = {(s,s[x|->i]) | (s,i) in E[e]}
C[c1;c2] = { (s,s') | exists s1.(s,s1) in C[c1] and (s1,s') in C[c2] }
C[if e then c1 else c2] =
{ (s,s') | E[e] s = i and i != 0 and (s,s') in C[c1] } U
{ (s,s') | E[e] s = 0 and (s,s') in C[c2] }
C[while e do c] = C[if e do {c;while e do c} else skip] =
{ (s,s') | E[e] s = i and i != 0 and (s,s') in C[c1;while e do c] } U
{ (s,s') | E[e] s = 0 and (s,s') in C[skip] } =
{ (s,s') | E[e] s = i and i != 0 and exists s1.(s,s1) in C[c1] and
(s1,s) in C[while e do c] } U { (s,s) | E[e] s = 0}
Oops. This last line isn't a definition -- it's an equation. Something
that we would like to hold, but we need to turn it into a definition
(i.e., solve for the "while e do c").
The way to do this is similar to what we did with the semantics of the
inference rules. Let's define:
F(E,C,S) = S U {(s,s') | (E(s) = 0 && s' = s) ||
(E(s) != 0 && exists s1.(s,s1) in C and
(s1,s') in S) }
Then we can take:
C[while e do c] = limit(F^i(E[e],C[c],{}))
The claim is that this satisfies the equation:
C[while e do c] = C[if e do {c;while e do c} else skip]
*