Introduction to Denotational Semantics So far we have see operational models for programming languages -- small-step and large-step. In this lecture I will present a different semantic model, called denotational semantics. The idea in denotational semantics is to express what mathematical functions programs compute. More precisely, we want to model programs as functions from input stores to output stores: given a program c, what is the function (from stores to stores) that c represents? As opposed to operational models, which tell us _how_ programs execute, the denotational model shows us _what_ programs compute. For a program c (a piece of syntax), we will denote by C[[c]] the _denotation_ of c, that is, the mathematical function that c computes: C[[c]] : Store -` Store Note that C[[c]] is actually a partial function (as opposed to a total function), because the program may not terminate for certain input stores; C[[c]] is not defined for those inputs, since they have no corresponding output stores. We must also model expressions as functions, this time from stores to the values they represent. We will denote by A[[a]] the denotation of arithmetic expressions, and by B[[b]] the denotation of boolean expressions: A[[a]] : Store -> Int B[[b]] : Store -> {true,false} Next I want to actually define these functions. To make it easier to write down these definitions, I will express (partial) functions as sets of pairs. More precisely, I will represent a partial map f : A -` B as a set of pairs F = {(a,b) | a in A and b = f(a) in B} such that, for each a in A, there is at most one pair of the form (a,_) in the set. Hence (a,b) in F is the same as b = f(a). Now I can define denotations for IMP. I will start with the denotations of expressions: A[[n]] = {(s,n) | s in Store} A[[x]] = {(s,s(x)) | s in Store} A[[a1 + a2]] = {(s,n) | s in Store /\ n = A[[a1]]s + A[[a2]]s } B[[true]] = {(s,true) | s in Store} B[[false]] = {(s,false) | s in Store} B[[a1 < a2]] = {(s,t) | s in Store /\ t = A[[a1]]s < A[[a2]]s} The denotations for commands are as follows: C[[skip]] = {(s,s) | s in Store} C[[x:=a]] = {(s,s[x->n]) | s in Store /\ n = A[[a]]s} C[[c1;c2]] = {(s,s') | s in Store /\ exists s'': (C[[c1]]s = s'' /\ C[[c2]]s''=s')} Note that C[[c1;c2]] = C[[c2]] o C[[c1]], where o is the composition of relations (composition of relations is defined as follows: if r1 subset A x B and r2 subset B x C then r2 o r1 subset A x C is r2 o r1 = {(a,c) | exists b in B: (a,b) in r1 /\ (b,c) in r2}). If C[[c1]] and C[[c2]] are total functions, then o is function composition. C[[if b then c1 else c2]] = {(s,s') | s in Store /\ B[[b]]s = true /\ C[[c1]]s = s'} U {(s,s') | s in Store /\ B[[b]]s = false /\ C[[c2]]s = s'} C[[while b do c]] = {(s,s) | s in Store /\ B[[b]]s = false} U {(s,s') | s in Store /\ B[[b]]s = true /\ exists s'': (C[[c]]s = s'' /\ C[[while b do c]]s'' = s')} But now I've got a problem: the last "definition" is not really a definition, it expresses C[[while b do c]] in terms of itself! In other words, it is not a definition, but a recursive equation. What I want is the solution of this equation. A simpler example To see the problem, consider a simpler example. Take a function f : NonnegInt -> NonnegInt such that: / 0, if x = 0 f(x) = | \ f(x-1) + 2x - 1, x > 0 This is an equation for f, not a definition. The solution to this equation is f(x) = x^2. Note that, in general, you may have equations that have no solutions (e.g., g(x) = g(x) + 1), or equations that have multiple solutions (e.g., g(x) = 4 g(x/2)). How can we compute the solution of such equations? The idea is to build the solution using successive approximations. For my equation for f, I start with a partial function f0 = {(0,0)} and compute successive approximations according to the recursive equation f0 = {(0,0)} f1 = if (x=0) then 0 else f0(x-1) + 2x - 1 = {(0,0), (1,1)} f2 = if (x=0) then 0 else f1(x-1) + 2x - 1 = {(0,0), (1,1), (2,4)} and so on. Essentially, this sequence gradually builds the function f(x) = x^2. I can model this process of successive approximations using a higher-order function F that take one approximation fk and returns the next approximation F{k+1}: F : (NonnegInt -` NonnegInt) -> (NonnegInt -` NonnegInt) F (f) = f', / 0, if x = 0 where f'(x) = | \ f(x-1) + 2x - 1, x > 0 My recursive equation says, in fact, that the function f that I'm looking for is such that f = F(f). In general, given a function F : A -> A, we say that a in A is a _fixed point_ of F is F(a) = a; such an element is denoted as a = fix(F). Hence, the solution of my recursive equation is a fixed point of the higher-order function F! And this fixed point can be computed iteratively, starting with f0 = {(0,0)}, and the, at each iteration, computing f{k+1} = F(fk). The fixed point is the limit of this process: f = fix(F) = f0 U f1 U f2 U f3 U .... Now I want to do the same for the denotation of while loops and express C[[while b do c]] as the fixed point of some higher-order function.