In the homework, I asked you to prove: |- eval(e,s) = i iff |- (e,s) evalsto i. which turns out to be much harder than it first looks. One direction is easy, but the other requires some cleverness. Here's a sketch (you can fill in the details): Defn: (e,s) =>n (e',s) means that there exists expressions e1,e2,...,en such that (e,s) => (e1,s) => (e2,s) => ... => (en,s) = (e',s) Note that if n = 0 then e = e'. Defn: (e,s) =>* (e',s) means that there exists an n >= 0 such that (e,s) =>n (e',s). Lemma A: If (e,s) =>* (i,s) iff eval(e,s) = i. Proof: In the forward direction, you argue by induction on n, where n is the number of steps that allows us to conclude that (e,s) =>* (i,s). In the backward direction, you argue by induction on the height of the tree that allows you to conclude eval(e,s) = i. Lemma B: If (e1,s) =>n (e1',s) then (e1+e2,s) =>n (e1'+e2,s) and (e1*e2,s) =>n (e1'*e2,s). Proof: By induction on n. Lemma C: If (e2,s) =>n (e2',s) then (i1+e2,s) =>n (i1+e2',s) and (i1*e2,s) =>n (i1*e2',s). Proof: By induction on n. Lemma D: If (e,s) evalsto i then (e,s) =>* (i,s). Proof: By induction on the height of the derivation that allows us to conclude (e,s) evalsto i. The base cases are easy. There are two inductive cases to consider. We'll deal with the one where the derivation ends with: (e1,s) evalsto i1 (e2,s) evalsto i2 ------------------------------------- (i = i1+i2) (e1+e2,s) evalsto i That is, e = e1+e2. By the induction hypothesis, we have (e1,s) =>* (i1,s) and (e2,s) =>* (i2,s). By Lemma B we have (e1+e2,s) =>* (i1+e2,s). By Lemma C we have (i1+e2,s) =>* (i1+i2,s). And then from the transition rules, we have (i1+i2,s) => (i,s). So, (e1+e2) =>* (i1+e2,s) =>* (i1+i2,s) =>(i,s). Thus, (e1+e2,s) =>* (i,s). Corr E: If (e,s) evalsto i then eval(e,s) = i. Lemma F: If (e,s) =>n (e',s) and (e',s) evalsto i, then (e,s) evalsto i. Proof: by induction on n. (Work it out for yourselves). Corr G: If eval(e,s) = i then (e,s) evalsto i. The theorem is thus established by Corr. E and Corr. G. -------------------------------------------------------------------------- Thus far, we've looked at operational models for our little programming languages. I want to briefly introduce you to two other modelling approaches: denotational semantics and axiomatic semantics. Today, we'll talk about denotational semantics for IMP. The basic idea is that we're going to translate each IMP command c into a mathematical (partial) function from stores to stores. A partial function F: A -> B is a set of pairs (x,y) such that x is an element of A and y is an element of B, and for any x in A, there is at most one y such that the pair (x,y) is in F. We'll begin by defining our denotational semantics by giving a partial function for the meaning of integer expressions as a partial function from stores to integers: E : Store -> Int E[i] = { (s,i) } E[x] = { (s,i) | s(x) = i} E[e1 + e2] = { (s,i) | (s,i1) in E[e1], (s,i2) in E[e2], and i=i1+i2 } E[e1 * e2] = { (s,i) | (s,i1) in E[e1], (s,i2) in E[e2], and i=i1*i2 } We'll next define a partial function for boolean expressions from stores to booleans: B[e1 <= e2] = { (s,true) | (s,i1) in E[e1] and (s,i2) in E[e2] and i1 <= i2 } U { (s,false) | (s,i1) in E[e1] and (s,i2) in E[e2] and i1 > i2 } Finally, we'll define a partial function from stores to stores to model our commands: C[skip] = { (s,s) } C[x := e] = { (s,s[x->i]) | (s,i) in E[e] } C[c1;c2] = { (s1,s2) | exists s. (s1,s) in E[c1] and (s,s2) in E[c2] } C[if e then c1 else c2] = { (s1,s2) | (s1,true) in B[e] and (s1,s2) in C[c1] } U { (s1,s2) | (s1,false) in B[e] and (s1,s2) in C[c2] } C[while e do c] = { (s1,s2) | (s1,s2) in C[if e then (c; while e do c) else skip] } This last equation is problematic because if you unwind the definition you see we have: C[while e do c] = { (s1,s1) | (s1,false) in B[e] } U { (s1,s2) | (s1,true) in B[e] and exists s.(s1,s) in C[c] and (s,s2) in C[while e do c]} So, we're trying to define the meaning of "while e do c" in terms of the meaning of "while e do c". But this is not a definition! To see the potential problem, consider the equation over integers: X = X + 1 Note that there is no solution to this equation. So, it's not always the case that a mathematical equation has a solution. Furthermore, even when there is a solution, there's no guarantee that it will be unique. For instance: 4 = X*X allows X to be both 2 and -2. If we truly want a mathematical definition that is well-founded, we need to come up with a way of constructing a unique definition for the meaning of while loops (without appealing to while loops.) So, we somehow need to solve for "C[while e do c]" and ensure that the solution is unique. Furthermore, the solution should satisfy the equation above (if our denotational model is going to capture our intended semantics.) One way we can do this is to build up the meaning of a while loop by conceptually unrolling it a bunch of times and then take the limit of the process. In particular, let us define F_e,c : (Store -> Store) -> (Store -> Store) as follows: F_e,c(G) = { (s,s) | (s,false) in B[e] } U { (s1,s2) | (s1,true) in B[e] and exists s. (s1,s) in C[c] and (s,s2) in G } So, F_e,c takes in a partial function (G) and acts like one iteration of the while-loop except that, instead of invoking itself when it needs to go around the loop again, it instead calls G. We can now define: C[while e do c] = Union(all i >= 0) F_e,c^i({}) That is, we can start with the empty set (which is a partial function that just happens to be defined nowhere) and apply F to it, then apply F to the result of that, and then apply F to the result of that and so on. We then take the union of all of these sets to get the total meaning of the while loop. In particular: C[while e do c] = {} + F({}) + F(F({})) + F(F(F({}))) + ... For example, let's consider what "while x <= y do x := x + 1" means by writing down all of the intermediate steps: F0 = {} F1 = {(s,s) | (s,false) in B[x <= y]} U {(s1,s2) | (s1,true) in B[x <= y] and (s1,s) in C[x := x + 1] and (s,s2) in F0 } = { (s,s) | s(x) > s(y) } F2 = {(s,s) | (s,false) in B[x <= y]} U {(s1,s2) | (s1,true) in B[x <= y] and (s1,s) in C[x := x + 1] and (s,s2) in F1 } = {(s,s) | s(x) > s(y) } U {(s1,s1[x->s1(x)+1]) | s1(x) <= s1(y) and s1(x)+1 > s1(y) } F3 = {(s,s) | s(x) > s(y) } U {(s1,s1[x->s1(x)+1]) | s1(x) <= s1(y) and s1(x)+1 > s1(y) } U {(s1,s1[x->s1(x)+2]) | s1(x) <= s1(y) and s1(x)+2 > s1(y) } F4 = {(s,s) | s(x) > s(y) } U {(s1,s1[x->s1(x)+1]) | s1(x) <= s1(y) and s1(x)+1 > s1(y) } U {(s1,s1[x->s1(x)+2]) | s1(x) <= s1(y) and s1(x)+2 > s1(y) } U {(s1,s1[x->s1(x)+3]) | s1(x) <= s1(y) and s1(x)+3 > s1(y) } If we take the union of all Fi then we get: C[while e do c] = {(s,s) | s(x) > s(y) } U {(s1,s1[x->s1(x)+n]) | s1(x) <= s1(y) and s1(x)+n > s1(y)) which makes sense. Now an interesting fact is that: F_e,c(C[while e do c]) is a subset of C[while e do c] That is, unrolling the loop any more doesn't do us any good. And, it turns out that we can prove (using induction on i) that our desired equation holds: C[while e do c] = { (s1,s1) | (s1,false) in B[e] } U { (s1,s2) | (s1,true) in B[e] and exists s.(s1,s) in C[c] and (s,s2) in C[while e do c]} I'll leave that as an exercise :-) -------------------------------------------------------------------------- Homework: hand in electronically using the course management system http://cms.csuglab.cornell.edu/. This is Problem Set 2 (PS2). Hand in one long .txt file with all of your solutions. Wrap SML-style comments (* ... *) around non-code solutions so that we can just load the file into SML to test it out. Make sure you test your code carefully. If it doesn't at least type-check, you'll get no credit at all. Last time, we talked about a simple imperative language (IMP). op in Op ::= + | - | * | <= e in Exp ::= x | i | true | false | e1 op e2 c in Com ::= skip | c1;c2 | x := e | if e then c1 else c2 | while e do c and gave a small-step semantics for the language. Part 1: Write down a large-step semantics for IMP. You should define two relations using inference rules: a. (e,s) evalsto (i,s) b. (c,s) evalsto s Part 2: Write SML code to implement your large-step semantics. In particular, we take the following datatypes as definitions for the abstract syntax: type var = string datatype op = Plus | Minus | Times | Lte datatype exp = Var of var | Int of int | True | False | Op of exp * op * exp datatype com = Skip | Seq of com*com | Assign of var * exp | If of exp * com * com | While of exp * com type store = var -> int You need to define the following functions: update_store : store * var * int -> store eval_int : exp * store -> int * store eval_bool : exp * store -> bool * store eval_com : com * store -> store Part 3: Extend your language to support the following new constructs (you don't need to hand in separate code for parts 2 and 3): a. operations ==, !=, <, >, >= for comparing integers b. do c while e -- should run the command c until e evaluates to false c. short-circuited boolean operations && and || Part 4: Using your big-step semantics prove that the following commands are equivalent (i.e., |- (c,s1) evalsto s2 iff |- (c',s1) evalsto s2). a. c1;(c2;c3) = (c1;c2);c3 b. (if e then c1 else c1) = c1 c. (y := i; x := 0) = (y := 0; x := i; while (1 <= x) do (y := y + 1; x := x - 1))