COP 4020 Lecture -*- Outline -*- * Kernel Language for the Declarative Computation Model ** declarative computation model details (2.2-2.5) *** single-assignment store (2.2) ------------------------------------------ SINGLE-ASSIGNMENT STORE (2.2) Oz's = operation unifies the two sides making them identical if possible local X1 X2 X3 X4 X5 X6 in %% (Feed the lines below one at a time...) {Browse store(x1:X1 x2:X2 x3:X3 x4:X4 x5:X5 x6:X6)} X1=X2 X3=X2 X5=name(label:7) X6=name(label:X1) X1=X4 X5=X6 end ------------------------------------------ See Semantics.oz Feed the lines above one at a time, have them explain what's going on Also draw pictures of the store ------------------------------------------ FOR YOU TO DO % What's shown by the following? local Y1 Y2 Y3 Y4 Y5 Y6 Y7 in {Browse store(y1:Y1 y2:Y2 y3:Y3 y4:Y4 y5:Y5 y6:Y6 y7:Y7)} Y1=4020 Y2=Y3 Y4=aRecord(first:Y5 second: 1234) Y6=aRecord(second:Y7 first:999) Y4=Y6 Y4=Y2 end ------------------------------------------ See Semantics.oz Q: How can we picture or model this? ------------------------------------------ MATHEMATICAL MODEL s in Store = Location -> PUValue where PUValue = Value + Set(Location) V in Value = Number + Record + Location + ... undetermined(s,x) = (s(x) in Set(Location)) determined(s,x) = (s(x) in Value) ------------------------------------------ (the book calls Locations "store variables" or simply "variables") The sets represent equivalence classes of undetermined locations. we assume it can be distinguished from Values A location, or the Value it may refer to, is a "store entity" ------------------------------------------ NOTATION FOR SINGLE-ASSIGNMENT STORES Example 1: s = { x1, x2 = x3 } means s(x1) = {x1} // x1 is undetermined s(x2) = {x2, x3} s(x3) = {x2, x3} pictured as: x1 [ *-]--->[undetermined {x1}] x2 [ *-]--->[undetermined {x2, x3}] x3 [ *-]-/ Example 2: { x1 = 541, x2 = 3|4|nil, x3 = 3|4|nil, x4 } pictured as: x1 [541] x2 [ *-]--->[ 3|*-]->[ 4|nil] x3 [ *-]--/ x4 [undetermined] ------------------------------------------ Emphasize that we're modeling the store as a (finite) function The notation just is a way to picture the function. Q: How would you implement this? Locations are addresses of memory cells. Values are addressed through the location's contents in memory, Values allocated on heap (in general). Q: What is a value store? one where all locations are associated with (complete) values. **** value creation (2.2.3) and operations on the store (2.8.2.2) ------------------------------------------ OPERATIONS ON THE STORE Allocation alloc: Store -> Variable x Store such that: alloc(s).1 not in dom(s) alloc(s).1 in dom(alloc(s).2) unbound(alloc(s).2, alloc(s).1) Binding of variables to values: bind: Store -> (Set(Variable) x Value) -> Store (bind(s)(ES, value))(y) = if y in ES then value else s(y) Binding of variables to variables: bind: Store -> Set(Variable) x Set(Variable) -> Store (bind(s)(ES1, ES2))(y) = if y in ES1 or y in ES2 then ES1 U ES2 else s(y) Example: bind({x1=x2, x3, x4=nil})({x1,x2}, 5) = {x1=5, x2=5, x3, x4=nil} Suppose s is {x1=x2=x3, x4, x5, x6=6, x7=7} What is: bind(s)({x4},{x5}) bind(s)({x4},{x1,x2,x3}) bind(s)({x5}, 5) bind(s)({x1,x2,x3}, 123) ------------------------------------------ ES stands for "Equivalence Set" Again, these are operations on functions ------------------------------------------ UNIFICATION OF VARIABLES (2.8.2.2) unify: Store -> (Variable x Variable) -> (Store + (Failure x Store)) where Failure = String unify(s)(x, y) = if unbound(s,x) and unbound(s,y) then bind(s)(s(x), s(y)) elseif unbound(s,x) and determined(s,y) then bind(s)(s(x), s(y)) elseif unbound(s,y) and determined(s,x) then bind(s)(s(y), s(x)) elseif determined(s,x) and determined(s,y) then if s(x) in Number and s(y) in Number then if s(x) == s(y) then s else ("equality...", s) end elseif s(x) in Closure and s(y) in Closure then if equalClosures(s(x), s(y) then s else ("equality...", s) end else // assuming both are records let l(l1:x1, ..., ln:xn) = s(x) l'(l1':y1,...,lm':ym) = s(y) in if l==l' and [l1,...,ln] == [l1',...,lm'] then listUnify(s)([x1,...,xn], [y1,...,ym]) else ("equality...", s) end E.g., suppose s is {x1=x2=x3, x4, x5=n(l:7), x6=n(l:x1)} What is: unify(s)(x1,x4) unify(s)(x4,x1) unify(s)(x1,x5) unify(s)(x5,x6) ------------------------------------------ The failure cases are used to communicate to the rest of the semantics. If there is a failure, side effects on the store are not undone, hence we need to pass back the store with the failure message. Q: Can you write listUnify? **** environment (2.2.4-5) ------------------------------------------ ENVIRONMENT E in Environment = VIdentifier -> Variable Example: Program text: X = 541 1. value creation: x1 = 541 2. identifier binding X = x1 Now have environment: E(X) = x1 store: s(x1) = 541 Pictured: E s X [ *-]----> x1 [541] Notation E = {X --> x1} s = {x1 = 541} ------------------------------------------ Q: What is the value of X? Q: What is dereferencing? ------------------------------------------ NOTATIONS Environment manipulations adjunction: (E + {X --> x})(Y) = if Y == X then x else E(Y) restriction: (E| )(Y) {X1, ...,Xn} = if Y in {X1, ..., Xn} then E(Y) else undefined ------------------------------------------ Q: What's the domain of the restriction? **** partial values (2.2.6) ------------------------------------------ PARTIAL VALUES (2.2.6) def: A *partial value* is a data structure that may contain unbound variables. Example: declare X Y X = person(name:"George", age: Y) What environment and store does this produce? ------------------------------------------ See Fig. 2.12 X [ *-]-------> x1 [ *-]--> [ person | * | *-]-> [age|*] | | v | [name | * ] | | | v | ["George"] | / /--------------------------------/ v Y [ *-]---------x2 [ *-]----------------------> [unbound {x2}] Q: What happens after the binding Y = "25" is done? ------------------------------------------ COMPATABILITY OF VALUES Declarative variables can be bound to variables. X = Y Pictured: X [ *-]-----> x1 [ *-]---> [unbound {x1, x2}] / Y [ *-]-----> x2 [ *-]-/ Whenever a value is bound to one, the other sees it. declare X Y X = Y Y = 25 {Browse X} ------------------------------------------ X [ *-]-----> x1 [ 25] Y [ *-]-----> x2 [ 25] Q: Can this work for more than 2 variables? Yes, with the right implementation (union-find data structure). **** dataflow variables (2.2.8) ------------------------------------------ DATAFLOW EXECUTION What if a variable is used before it is bound? 1. Get garbage from memory 2. Get default value for type (0) 3. Give error message and stop 4. Illegal, complain at compile time 5. Wait until variable is bound ------------------------------------------ Q: Why do these? Q: What languages do these? 1: C++, 2: Java fields and arrays, 3: Prolog in arithmetic, 4: Java for locals 5: Oz Q: What does Oz do? *** Kernel Language (2.3) **** Syntax (2.3.1) ------------------------------------------ KERNEL LANGUAGE SYNTAX (TABLES 2.1, 2.2) ::= skip | | local in end | = | = | if then else end | case of then else end | '{' {} '}' ::= ::= | | ::= | ::= | ( {:} ) ::= proc '{' $ {} '}' end ::= | ::= | | ::= true | false ------------------------------------------ Here , are an identifiers Note: variable identifiers can also be written in backquotes Q: Why would you include all of these in the language? E.g., why skip? Q: Why leave out some things, e.g., = ? can get away with it at little cost Q: What is a case statement like in C, C++, or Java? Q: why records? needed for data completeness *** Kernel Language Semantics (2.4) **** The abstract machine (2.4.2) This semantics is a terminal transition system (Plotkin), little step (C. Gunter), or computation (M. Hennessy) semantics essentially using rewriting as a universal machine ***** little step semantics in general To give the semantics of a programming language, use two auxiliary functions: input and output ------------------------------------------ COMPUTATION (LITTLE STEP) SEMANTICS Meaning Programs <-------> Answers | ^ input | | output | | v -->* | State ------------> T def: a is in Meaning[[P]] iff ------------------------------------------ the Meaning relation is often partial, and defined by going around the diagram ... there is a g in T such that input[[P]] -->* g and output(g) = a. Meaning[[P]] is a function when -->* is Church-Rosser (confluent) ***** terminal transition systems this is the guts of the system, defining the abstract machine by defining --> ------------------------------------------ TERMINAL TRANSITION SYSTEM (TTS) (State, -->, T) State: -->: T: -->* reflexive, transitive closure ------------------------------------------ ... a set of configurations (e.g., g) i.e., configurations of the abstract machine ... a binary relation on State Note that --> may be partial The --> relation (Hennessy) is often written as ==> ... subset of terminal configurations, must be such that if g in T, then there is no g' such that g --> g' Q: What are the possible outcomes for a program? a set of terminal states, an infinite computation, or getting stuck! ***** TTS for Oz ------------------------------------------ TTS FOR OZ (ST,s) in State = Stack x Store + Message Stack = ( x Environment)* T = {(nil,s) | s in Store} + Message Message = String input[[S]] = ((S,{})|nil, {}) output((nil,s)) = s output(Msg) = Msg ------------------------------------------ As an alternative, one could define, for example, output((nil,s)) = s(x_1) assuming that x_1 is some standard "answer" location (write programs as local Answer in ... end ) The messages are used for halting Oz with an error (e.g., for uncaught exceptions) Q: What states are terminal? Q: How should we define the transitions (-->)? ------------------------------------------ TRANSITIONS (-->) [skip] ((skip, E) | Rest, s) --> (Rest, s) [sequence] ((S1 S2, E) | Rest, s) --> ((S1, E) | (S2, E) | Rest, s) [local] ((local X in S end,E)|Rest, s) --> ((S,E')|Rest, s') where E' = E+{X-->x} and x#s' = alloc(s) [var-var binding] ((X=Y, E) | Rest, s) --> (Rest, s') where s' = unify(s)(E(X), E(Y)) and isStore(s') [var-var bindingerror] ((X=Y, E) | Rest, s) --> ((raise failure(Msg) end, E) | Rest, s') where (Msg,s') = unify(s)(E(X), E(Y)) [value creation] ((X=V, E) | Rest, s) --> (Rest, s3) where y#s' = alloc(s) and v = MV[[V]](E) and s'' = bind(s')({y},v) and s3 = unify(s'')(E(X),y) and isStore(s3) [value creation error] ((X=V, E) | Rest, s) --> ((raise failure(Msg) end, E) | Rest, s3) where y#s' = alloc(s) and v = MV[[V]](E) and s'' = bind(s')({y},v) and (Msg, s3) = unify(s'')(E(X),y) [if-true] ((if X then S1 else S2 end,E)|Rest, s) --> ((S1, E)|Rest, s) where determined(s, E(X)) and s(E(X)) == true [if-false] ((if X then S1 else S2 end, E)|Rest, s) --> ((S2, E)|Rest, s) where determined(s, E(X)) and s(E(X)) == false [if-error] ((if X then S1 else S2 end, E)|Rest, s) --> ((raise error(...) end, E)|Rest, s) where determined(s, E(X)) and not(s(E(X)) == true) and not(s(E(X)) == false) [application] (({X Y1 ... Yn}, E)|Rest, s) --> ((Body, E')|Rest, s) where determined(s,E(X)) and s(E(X)) in Closure and [Z1 ... Zn] = args(s(E(X))) and Body = body(s(E(X))) and E' = env(s(E(X))) + {Z1 -->E(Y1)} + ... + {Zn -->E(Yn)} [application-error] (({X Y1 ... Yn}, E)|Rest, s) --> ((raise error(...),E) | Rest, s) where determined(s,E(X)) and not(s(E(X)) in Closure) or s(E(X)) does not have n arguments [case-match] ((case X of L(F1: X1 ... Fn:Xn) then S1 else S2 end, E)|Rest, s) --> ((S1, E') | Rest, s) where determined(s,E(X)) and isRecord(s(E(X))) and Label(s(E(X))) == L and Arity(s(E(X))) == [F1 ... Fn] and E' = E + {X1 -->s(E(X)).F1, ..., Xn -->s(E(X)).Fn} [case-else] ((case X of L(F1: X1 ... Fn:Xn) then S1 else S2 end, E)|Rest, s) --> ((S2, E) | Rest, s) where determined(s,E(X)) and not(isRecord(s(E(X)))) or not(Label(s(E(X))) == L) or not(Arity(s(E(X))) == [F1 ... Fn]) ------------------------------------------ Could split the [value creation] into 2 rules, [value creation to unbound] ((X=V, E) | Rest, s) --> (Rest, s') where unbound(s, E(X)) and v = MV[[V]](E) and s' = bind(s)(s(E(X)),v) [value unification] ((X=V, E) | Rest, s) --> (Rest, s3) where determined(s, E(X)) and y#s' = alloc(s) and v = MV[[V]](E) and s'' = bind(s')({y},v) and s3 = unify(s'')(E(X),y) and isStore(s3) which would generate garbage less quickly, but is harder to work with. Q: What happens if one of the variables in var-var binding is not in the domain of the environment, E? it's illegal as a program, so rejected Q: What free variables are allowed in a program? none in real Oz, some standard environment is used Q: What's the parameter passing mechanism? Call by reference! Q: What happens to an if-statement when the condition variable is not determined? gets stuck suspends Q: Can the matching case change the store? ------------------------------------------ MEANING OF VALUE EXPRESSIONS MV: ValueExpression -> Environment -> Value MV[[X]](E) = E(X), where X in MV[[N]](E) = N, where N in MV[[L]](E) = L, where L in MV[[L(F1:X1, ..., Fn:Xn)]](E) = L(F1: MV[[X1]](E), ..., Fn: MV[[X1]](E)), where L(F1:X1, ..., Fn:Xn) in MV[[proc {$ F1 ... Fn} Body end]](E) = (proc {$ F1 ... Fn} Body end, E|FVP), where FVP = FV(Body) \ {F1 ... Fn} is the set of free identifiers in the procedure ------------------------------------------ Note that this is a denotational semantics The value of a procedure is a Closure, which is a pair of the procedure's text and the environment restricted to its free identifiers. ***** Examples (2.4.5) ------------------------------------------ EXAMPLES let S1 = local X in S2 end let S2 = local R in X=2 R=X end Then ((S1,{})|nil), {}) --> ((S2, {X-->x1})|nil, {x1}) --> ((X=2 R=X, {X-->x1,R-->x2})|nil, {x1,x2}) --> ((X=2,{X-->x1,R-->x2})| (R=X,{X-->x1,R-->x2})|nil, {x1,x2}) --> ((R=X,{X-->x1,R-->x2})|nil, {x1=2,x2,x3=2}) --> (nil, {x1=2,x2=2,x3=2}) ------------------------------------------ Q: Is this final state terminal? Yes Can think of the output of this as the state of some particular variable in the store (say x1). ------------------------------------------ ANOTHER EXAMPLE local X in local Y in Y = proc {$ ?R} R=X end X=true local X in X=false local Z in {Y Z} if Z then skip else Z=X end end end end end let S0 = local X in S1 end % statements S1 = local Y in S2 S3 S4 end S2 = Y = proc {$ ?R} R=X end S3 = X=true S4 = local X in S5 S6 end S5 = X=false S6 = local Z in {Y Z} S7 end S7 = if Z then skip else Z=X end E2 = {X-->x1, Y-->x2} % environments E3 = {X-->x5, Y-->x2} E4 = {X-->x5, Y-->x2, Z-->x6} C1 = (proc {$ ?R} R=X end, {X-->x1}) % a closure s5 = {x1=true,x2=C1,x3=C1,x4=true,x5} % some stores s6 = {x1=true,x2=C1,x3=C1,x4=true,x5,x6} s7 = {x1=true,x2=C1,x3=C1,x4=true,x5,x6=true} We calculate as follows... ((local X in S1 end,{})|nil, {}) --> ((local Y in S2 S3 S4 end,{X-->x1})|nil, {x1}) --> ((S2 S3 S4,{X-->x1,Y-->x2})|nil, {x1,x2}) --> ((Y = proc {$ ?R} R=X end,E2)|(S3 S4,E2)|nil, {x1,x2}) --> ((X=true,E2)|(local X in S5 S6 end,E2)|nil, {x1,x2=C1,x3=C1}) --> ((local X in S5 S6 end,E2)|nil, {x1=true,x2=C1,x3=C1,x4=true}) --> ((S5 S6,{X-->x5,Y-->x2})|nil, s5) --> ((local Z in {Y Z} S7 end,E3)|nil, s5) --> x6}> (({Y Z} S7,E4)|nil, s6) --> (({Y Z},E4)|(S7,E4)|nil, s6) --> ((R=X,{X-->x1,R-->x6})|(S7,E4)|nil, s6) --> ((if Z then skip else Z=X end,E4)|nil, s7) --> ((skip,E4)|nil, s7) --> (nil, s7) ------------------------------------------ Emphasize the application step and how it treats environments. If you want another example of applications, try local K in local A in K = proc {$ X ?R} R=proc {$ Y ?Z} Z=X end end local F in local Three in {K Three F} local Four in {F 4 A} end end end end end Or do a case example...