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]