COP 4020 Lecture -*- Outline -*- * Kernel Language for the Declarative Computation Model Based on Peter van Roy and Seif Haridi's book, "Concepts, Techniques, and Models of Computer Programming" (MIT Press, 2004), where all references that are not otherwise attributed are found. Basic goal is to give you a deep understanding of the way expressions can be modeled using simple statements. Bring to class: printout of C++ solution to closure problem (cadd_corrected.cpp) ** motivation (2) Q: What does programming involve? ------------------------------------------ PARTS OF PROGRAMMING ------------------------------------------ ... - a programming model - a computation model - a set of reasoning techniques (correctness and efficiency) This class studies computation models that are useful for programmers and associated programming models. First: the declarative model Declarative model has simple reasoning techniques and can be made efficient. It's also fundamental - to lots of programming languages esp. functional and logic ones - embodies fundamental theory of *expressions* Can be made concurrent without losing good properties ** Defining languages (2.1) *** how languages are defined ------------------------------------------ HOW LANGUAGES ARE DEFINED ------------------------------------------ ... Define languages by giving its syntax and semantics Syntax in terms of context free grammar + typing rules Semantics can be done in several ways, including operationally Also: pragmatics, e.g., efficiency considerations *** syntax ------------------------------------------ EXTENDED BACKUS-NAUR FORM (EBNF) Example ::= { } ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 ------------------------------------------ Describe this as a game (p. 33) to make a derivation. The book defines partial grammar rules using '...' in a production ------------------------------------------ FOR YOU TO DO Give an EBNF grammar for phone numbers of the form 555-1212 or 441-1572 ------------------------------------------ *** semantics (2.1.2) ------------------------------------------ SEMANTICS Goals: - simple - mathematical - reason about correctness - reason about efficiency Kernel Language Approach: [ Practical Language ] | | translation (desugaring) v [ Kernel Language ] E.g., fun {Sqr X} X * X end -translates-to-> proc {Sqr X Y} {Number.'*' X X Y} end ------------------------------------------ Q: How to define the kernel language? in some other way (e.g., operational semantics) Q: What characteristics needed for kernel language? Why? - Turing complete - minimal - easy to translate into, without destroying structure - easy to understand - easy to reason about - has formal semantics Q: Is C a kernel language? C++? no **** linguistic abstractions In Oz, "fun" is a linguistic abstraction, it gets translated into "proc" Q: What is a linguistic abstraction? An named abstraction that adds to a language, e.g. a for loop, functions (fun), classes, etc. ------------------------------------------ LINGUISTIC ABSTRACTIONS ------------------------------------------ Can be defined by macros (translation) Can also help efficiency (e.g. switch statement in C/Java) Q: What are some linguistic abstractions in C++ or Java? "for", "interface" (an abstract class with only abstract methods) **** syntactic sugar (Like linguistic abstraction but not named) E.g., in Oz, can leave out "local" and "end" as if N == 1 then [1] else L in % ... end is sugar for if N == 1 then [1] else local L in % ... end end Q: What is a syntactic sugar? Q: How is it different from a linguistic abstraction? ------------------------------------------ SYNTACTIC SUGARS ------------------------------------------ ... a shorthand, but not separately named, so not a new abstraction. Note: Many authors don't make this distinction. Q: What are some syntactic sugars in C++ and Java? single statement in a loop body, implicit coercions for numbers (autoboxing, like Integer x; x=7; ==> Integer x; x = new Integer(7);) **** Examples ------------------------------------------ EXAMPLES OF DESUGARING LINGUISTIC ABSTRACTIONS AND SUGARS fun {Discrim A B C} fun {Square N} N*N end in {Square A} + {Square B} - 4.0*A*C end {Browse {Discrim 5.0 4.0 3.0}} --> Discrim = proc {$ A B C Res} local Square in Square = proc {$ N R} {Number.'*' N N R} end local A2 in local B2 in {Square A A2} {Square B B2} local A2B2 in {Number.'+' A2 B2 A2B2} local A4 in local Four in Four=4.0 {Number.'*' A Four A4} local A4C in {Number.'*' A4 C A4C} {Number.'-' A2B2 A4C Res} end end end end end end end end local Temp in local Five in local Four in local Three in Five=5.0 Four=4.0 Three=3.0 {Discrim Five Four Three Temp} {Browse Temp} end end end end ------------------------------------------ See Discrim.oz and DiscrimDesugared.oz ------------------------------------------ FOR YOU TO DO Desugar the following: fun {Inc N} N+1 end {Browse {Inc 3}} ------------------------------------------ See Inc.oz and IncDesugared.oz ** declarative computation model details (2.2-2.5) *** data and the store ------------------------------------------ VALUE CREATION: WHAT IS SHOWN? local X Y Z in %% Value creation X=3 Y=oh#kay Z=zrec(z1:99 32) %% atoms vs. identifiers {Browse x} {Browse X} {Browse Y} {Browse Z} end ------------------------------------------ See Semantics.oz run the above, Get them to predict output... ------------------------------------------ FOR YOU TO DO % What's shown by the following? local D in D=drec(3 feature: atom) {Show rec(feature:3)} {Show D} end ------------------------------------------ the point is just the distinction between variables and values i.e., that {Show D} doesn't print the name D, but the value of D *** single-assignment store (2.2) ------------------------------------------ SINGLE-ASSIGNMENT STORE (2.2) Oz's = operation (tell) unifies the two sides making them identical if possible local X1 X2 X3 X4 X5 X6 in {Browse store(x1:X1 x2:X2 x3:X3 x4:X4 x5:X5 x6:X6)} {Delay 5000} X1=X2 {Delay 2000} X3=X2 {Delay 2000} X5=name(label:7) {Delay 2000} X6=name(label:X1) {Delay 2000} X1=X4 {Delay 2000} 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? Draw a picture (see below) Mathematical model: s in Store = Location -> PUValue (the book calls Locations "store variables" or simply "variables") where PUValue = Value + Set(Location) V in Value = Number + Record + Location + ... The sets represent equivalence classes of undetermined locations. we assume it can be distinguished from Values undetermined(s,x) = (s(x) in Set(Location)) determined(s,x) = (s(x) in Value) 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 {x4}] ------------------------------------------ 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 undetermined variables as a circular list, perhaps 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. Show code in ../../Reducer/Store.oz **** environment (2.2.4-5) Q: What does the following show? ------------------------------------------ ENVIRONMENT AND SCOPING EXAMPLES local X Y in X=4 Y=5 {Show 'first X = '#X} {Show 'first Y = '#Y} local X Y in X=11 Y=22 {Show 'inner X = '#X} {Show 'inner Y = '#Y} end {Show 'second X = '#X} {Show 'second Y = '#Y} end local X XVal Add AddPairs in X=4 XVal = proc {$ ?R} R=X end Add = fun {$ X Y} X+Y end fun {AddPairs X Y} case X of X1#X2 then case Y of Y1#Y2 then (X1+Y1)#(X2+Y2) end end end {Show '{XVal} = '#{XVal}} {Show '{Add X Y} = '#{Add 20 4000}} {Show '{AddPairs 50#21 40#20} = ' # {AddPairs 50#21 40#20}} end ------------------------------------------ See EnvironmentExamples.oz ------------------------------------------ CASE AND SCOPING % What does the following do? local BadAddPairs in fun {BadAddPairs X Y} case X of X#Y then case Y of X#Y then (X+Y)#(X+Y) else canthappen end end end {Show '{BadAddPairs 50#21 40#20} = ' # {BadAddPairs 50#21 40#20}} end ------------------------------------------ See EnvironmentExamples.oz Q: How can we formally model these examples? Use an environment: E in Environment = Identifier -> Location ------------------------------------------ SUMMARY OF NOTATIONS AND MODEL Example: Y = 4020 Now have environment: E(Y) = x1 store: s(x1) = 4020 Pictured: E s Y [ *-]----> x1 [4020] Notation E = {Y --> x1} s = {x1 = 4020} ------------------------------------------ Q: What is the value of Y? Q: What is dereferencing? Show ../../Reducer/Environment.oz **** partial values (2.2.6) ------------------------------------------ PARTIAL VALUES (2.2.6) def: A *partial value* is a data structure that may contain undetermined locations. Example: local X Y in X = person(name:"George", age: Y) end What environment and store does this produce? ------------------------------------------ See Fig. 2.12 name age X [ *-]-------> x1 [ *-]--> [ person | * | *-]--------\ | | \ | \ | \ | v | ("George") | / /--------------------------------/ v Y [ *-]---------x2 [ *-]----> [undetermined {x2}] Q: What happens after the binding Y = "25" is done? ------------------------------------------ COMPATABILITY OF VALUES Program identifiers can be unified with (other) identifiers, which makes them the same (or checks that they are, fails if can't) if they are compatible: X = Y Pictured: X [ *-]-----> x1 [ *-]---> [undetermined {x1, x2}] / Y [ *-]-----> x2 [ *-]-/ Whenever one value is determined, the other variable identifier also sees it. local X Y in X = Y Y = 25 {Browse X} end ------------------------------------------ Might be implemented more like: X [ *-]-----> x1 [ 25] Y [ *-]-----> x2 [ 25] Q: Can this work for more than 2 identifiers? Yes, with the right implementation (union-find data structure). **** dataflow execution (2.2.8) ------------------------------------------ DATAFLOW EXECUTION What if a location is used before it is determined? 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 location's value is determined ------------------------------------------ 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) This syntax defines - the interface of the kernel computation model - the target syntax of desugaring ------------------------------------------ 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: identifiers can also be written in single quotes We'll also consider things like Number.'+' to be Identifiers 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 **** Values and types (2.3.2) ------------------------------------------ TYPES def: a *type* is a set of values with a set of operations Types in the Oz's declarative model Value Number Int Char Float Record Tuple Literal Bool Atom ... List String ... Procedure ... ... ------------------------------------------ See Figure 2.16 can use IsT to test membership in T, e.g., IsProcedure Q: What does v has type T mean? Q: What does a subtype mean in this interpretation of types? Q: What is dynamic typing? Why do it? Q: How does this type system compare to that of C, C++, and Java? ***** Basic types (2.3.3) See the book, already covered ***** Records (2.3.4-5) ------------------------------------------ RECORDS (2.3.4) Creation newrec(field1: Val1) Operations: ------------------------------------------ Q: What's the operation for creating a record? Just write it down! Q: What are the operations on records? See 2.3.5: Arity, Label, and '.' for field selection. Arity gives a list of field names. (IsRecord tests if it's a record) Q: What are these kinds of operations called in Java? reflection Q: How can records be used to make enumerations? variants? Q: What does == do for records? compares for structural equality, not object identity! What's that like in Java? .equals(Object) Q: What is the difference between X = Y and X == Y ? ***** Procedures (2.3.4-5) Functions desugar into procedures, which are values in the kernel. ------------------------------------------ PROCEDURES Creation: proc { $ X Y } Y = X+X end Operations: ------------------------------------------ Q: What are the operations on procedures? See p. 55 creation (with proc) and calling (with {}) *** Kernel Language Semantics (2.4) **** basic concepts (2.4.1) ***** Static Scoping ------------------------------------------ DECLARATIONS def: In the declarative kernel, an identifier is declared by: 1. a local statement of form local in end e.g., Y is declared in: local Y in R=Y end 2. the identifiers in the of a case statments then where ::= ::= | ( {:} ) e.g., Y is declared in: case Z of foo(ab:Y) then R=Y else R=nil end 3. the formal parameter of a procedure value of form proc '{' $ {} '}' end e.g., Y and R are declared in: proc {$ Y ?R} R=Y end In each of the above, the *region* where such a declaration of may be referred to by uses of is the statement . ------------------------------------------ Q: What other declaration sites are there in the sugared part of Oz? ------------------------------------------ DECLARATIONS IN SUGARED OZ SYNTAX local foo(ab: Y cd: Z) = Q in ... end case Q of foo(ab: Y) then ... [] bar(cd: Z) then... else ... end fun {Id X} X end fun {Head X|_} X end ------------------------------------------ these show: local expressions, patterns of case expressions, formals of function values, patterns used in function and procedure formals, (note: Id and Head are themselves not formals!) not shown: declare, declare ... in ------------------------------------------ STATIC SCOPING def: In *static scoping*, each identifier, X, def: In *dynamic scoping*, each identifier, X, Example: local X F T in local X in X=2 F = proc {$ Y ?Z} Z=X+Y end end X=1 {F 10 T} {Browse T} end ------------------------------------------ ... denotes the location for X declared by the closest textually surrounding declaration of X This is also known as lexical scoping ... denotes the location for X declared by the most recent declaration for X that is still active. The ? in the declaration of F is a comment indicating the output parameter. Q: What does the example give with each kind of scoping? ------------------------------------------ PICTURE WITH DYNAMIC SCOPING ------------------------------------------ ... X [ ] F [ *-]---[proc| | ] T [ ] X [ 2 ] then X [ ] F [ *-]---[proc| | ] T [ ] Y [ 10 ] Z [ 11 ] Q: In the example, does it matter if X=1 occurs after the declaration of F? Q: What is the meaning of the procedure local X in X = 541 proc {$ Y Z} Z = X+Y end end with dynamic scoping? Q: What kind of scoping is in C, C++, and Java? The Unix shell? ***** Free and bound identifier occurrences (p. 64) Have to distinguish between uses and declarations of identifiers ------------------------------------------ FREE AND BOUND IDENTIFIER USES def: an identifier *occurs free* in a statement iff def: an identifier *occurs bound* in a statment iff contains a use of that refers to a declaration of within . {proc {$ X ?Y} Y=X end F1 Z} {{{proc {$ X ?Y} Y=proc {$ X ?Y} Y={X F} end end} F Z} F1 Z1} ------------------------------------------ ... contains a use of , which does not refer to a declaration of within Q: How would you define free and bound identifier uses in expressions? The same way. Q: in the first expression, what does X refer to? Draw arrows from uses to declarations in the two examples Be sure they understand what "intervening declarations" mean before going on ------------------------------------------ EXAMPLES F, F1 occur free in: F1 {F F1} proc {$ B ?Res} Res=F end B, B1 occur bound in: proc {$ B ?Res} Res = B end proc {$ B1 ?R1} R1= proc {$ B ?R} R={B1 B} end end There can be a mix: {proc {$ B R} R=B end F} ^ ^ bound-/ \-free occurrence occurrence The same identifier can occur both ways: {fun {$ N} N end N} ^ ^ bound-/ \-free occurrence occurrence Identifiers that are free in a subexpression may be bound in a larger expression fun {$ F} {fun {$ B} {B F1} end F} end Identifiers must be used to be bound proc {$ N R} proc {$ N R2} R2=3 end end fun {$ N} 3 end ------------------------------------------ Q: So if N occurs free in an expression, does that mean it doesn't occur bound? ------------------------------------------ FOR YOU TO DO What are the (a) free, and (b) bound identifiers in ... fun {$ X} fun {$ Y} X end end {G {Tail X}} fun {$ X} {G {Tail X}} end fun {$ G} fun {$ X} {G {Tail X}} end end ------------------------------------------ Q: What's the difference between an identifier being bound in an expression and a location being bound in the store? It's having a name declared (bound identifier) vs. having a value in the store (determined store variable) Q: Can an identifier that is free in an expression refer to a location that has a determined value in the store? Yes, consider {Browse 3}, where presumably Browse denotes a location that is determined in the store. ------------------------------------------ FORMAL DEFINITIONS FOR THE KERNEL % FV() is "the set of free identifiers in " FV(skip) = {} FV( ) = FV() U FV() FV(local in end) = FV( = ) = {, } FV( = ) = {} U FVE() FV(if then else end) = {} U FV() U FV() FV({ ... }) = {, , ..., } FV(case of then else end) = % FVE() is "the set of free identifiers in " FVE() = {} FVE() = {} FVE((: ... )) = FVE(proc {$ ... } end) = % BV() is "the set of bound identifiers in " BV(skip) = {} BV( ) = BV() U BV() BV(local in end) = BV( = ) = {} BV( = ) = BVE() BV(if then else end) = BV() U BV() FV({ ... }) = {} BV(case of then else end) = % BVE() is "the set of bound identifiers in " BVE() = {} BVE() = {} BVE((: ... )) = BVE(proc {$ ... } end) = ------------------------------------------ ... FV(local in end) = FV() - {} FV(case of then else end) = {x} U (FV() - FVE()) U FV() FVE((: ... )) = {, ..., } FVE(proc {$ ... } end) = FV() - {, ..., }) BV(local in end) = BV() U (FV() \intersect {}) BV(case of then else end) = (BV() U BV() U (FV() \intersect FVE()) BVE((: ... )) = {} BVE(proc {$ ... } end) = BV() U (FV() \intersect {, ..., }) The intersections happen because a bound variable identifier only occurs when it is both declared and used. Q: How would you generalize these to more complex expressions? local and case handled the same but use FVE and BVE, patterns in function formals are also declarations... See the FreeVarIds.oz and BoundVarIds.oz programs in the reducer ***** procedure values or closures (p. 65) Named after Haskell Curry, a logician (although it was actually invented by Frege and Schoenfinkel) ------------------------------------------ CURRYING function: Append3 = fun {$ LS1 LS2 LS3} {Append LS1 {Append LS2 LS3}} end curried form: CAppend3 = fun {$ LS1} fun {$ LS2} fun {$ LS3} {Append LS1 {Append LS2 LS3}} end end end Use of it: {{{CAppend3 [1 2]} [3 4]} [5 6]} FOR YOU TO DO Curry the following definition: Add = fun {$ X Y} X+Y end Use the curried version to add 2 and 3 ------------------------------------------ Q: How is the binding of LS1 to [1 2] remembered? in a closure, when the application is made we start from that environment Q: Can this be done in C++? ------------------------------------------ CURRYING IN C++? #include typedef int (*func)(int); int takes_y(int y) { return(x + y); } func cadd(int x) { return(&takes_y); } int main() { cout << (cadd(2))(3) << endl; } ------------------------------------------ Q: does this work? no, what's the value of x in takes_y? To solve that problem, simulate the notion of a closure // corrected C++ program #include typedef int (*func)(int, int); class closure { public: closure(int x_val, func f_val) : x(x_val), f(f_val) {} int call(int arg) { return f(x, arg); } private: const int x; const func f; }; int add(int x, int y) { return x + y; } closure* cadd(int x) { return new closure(x, add); } int main() { cout << cadd(2)->call(3) << endl; } (This is one reason we don't use C/C++, it's too hard to do this...) The following shows that in Physics, currying is also useful... ------------------------------------------ GRAVITATIONAL FORCE FIELDS AS CURRIED FUNCTIONS declare G = 6.670e~11 %% UNITS: N * m^2 / kg^2 fun {Square R} %% UNITS: m -> m^2 R*R end fun {GravForce M1 R M2} %% UNITS: kg x m x kg -> N if R == 0.0 then 0.0 else G * M1 * M2 / {Square R} end end fun {GravField M1} %% UNITS: kg -> m -> kg -> N fun {$ R} fun {$ M2} if R == 0.0 then 0.0 else G * M1 * M2 / {Square R} end end end end %%% USING IT MassOfEarth = 5.96e24 %% UNITS: kg RadiusOfEarth = 6.37e6 %% UNITS: m EarthsField %% UNITS: m -> kg -> N = {GravField MassOfEarth} ForceAtSurface %% UNITS: kg -> N = {EarthsField RadiusOfEarth} ------------------------------------------ See GravForceExample.oz and GravForceExampleTest.oz ------------------------------------------ PROCEDURE VALUES ARE CLOSURES def: a *closure* is: code for a procedure and an environment ------------------------------------------ environment give values for free identifiers Q: So, in general, what in C++ is like a closure? an object: it has a little environment (data members) and code (member functions) But again, in C++, don't have anonymous classes, and can't capture the environment at run-time without preparing with class definition ahead of time. **** The abstract machine (2.4.2) (offline, discuss reducer program) Skip, but if you show anything, concentrate on case statements. 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 (MST,s) in State = state(MST x Store) + msg(String) MST = MultiSet(Thread) Thread = runnable(Stack) + suspended(Stack x Location) Stack = List( x Environment)) T = {({runnable(nil),...,runnable(nil)},s) | s in Store} + Message input[[S]] = ({runnable(S,{})|nil}, {}) output({runnable(nil),...,runnable(nil)}, s)) = s output(msg(Msg)) = Msg ------------------------------------------ As an alternative, one could define, for example, output({runnable(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] (runnable((skip,E) | Rest), s) --> (runnable(Rest), s) [sequence] (runnable((S1 S2, E) | Rest), s) --> (runnable((S1, E) | (S2, E) | Rest), s) [local] (runnable((local X in S end,E)|Rest), s) --> (runnable((S,E')|Rest), s') where E' = E+{X-->x} and x#s' = alloc(s) [var-var binding] (runnable((X=Y, E) | Rest), s) --> (runnable(Rest), s') where s' = unify(s)(E(X), E(Y)) and isStore(s') [var-var bindingerror] (runnable((X=Y, E) | Rest), s) --> (runnable((raise failure(Msg) end, E) | Rest), s') where (Msg,s') = unify(s)(E(X), E(Y)) [value-creation] (runnable((X=V, E) | Rest), s) --> (runnable(Rest), s3) if undetermined(s, E(X)) then v = MV[[V]](E) and s3 = bind(s)(E(X),v) else 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] (runnable((X=V, E) | Rest), s) --> (runnable((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] (runnable((if X then S1 else S2 end,E)|Rest), s) --> (runnable((S1, E)|Rest), s) where determined(s, E(X)) and s(E(X)) == true [if-false] (runnable((if X then S1 else S2 end, E)|Rest), s) --> (runnable((S2, E)|Rest), s) where determined(s, E(X)) and s(E(X)) == false [if-error] (runnable(if X then S1 else S2 end, E)|Rest), s) --> (runnable((raise error(...) end, E)|Rest), s) where determined(s, E(X)) and not(s(E(X)) == true) and not(s(E(X)) == false) [application] (runnable(({X Y1 ... Yn}, E)|Rest), s) --> (runnable((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] (runnable(({X Y1 ... Yn}, E)|Rest), s) --> (runnable((raise error(...) end,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] (runnable((case X of L(F1: X1 ... Fn:Xn) then S1 else S2 end, E)|Rest), s) --> (runnable((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] (runnable((case X of L(F1: X1 ... Fn:Xn) then S1 else S2 end, E)|Rest), s) --> (runnable((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 undetermined] (runnable((X=V, E) | Rest), s) --> (runnable(Rest), s') where undetermined(s, E(X)) and v = MV[[V]](E) and s' = bind(s)(s(E(X)),v) [value unification] (runnable((X=V, E) | Rest), s) --> (runnable(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 identifiers in var-var binding is not in the domain of the environment, E? it's illegal as a program, so rejected Q: What free identifiers 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's identifier denotes a location that 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[[Xn]](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 local R in local X in X = 2 R = X end end ({runnable((local R in local X in X = 2 R = X end end, {})|nil)}, {}) --> {by [local]} ({runnable((local X in X = 2 R = X end, {R-->x0})|nil)}, {x0}) --> {by [local]} ({runnable((X = 2 R = X, {R-->x0, X-->x1})|nil)}, {x0, x1}) --> {by [sequence]} ({runnable((X = 2, {R-->x0, X-->x1})|(R = X, {R-->x0, X-->x1})|nil)}, {x0, x1}) --> {by [value-creation]} ({runnable((R = X, {R-->x0, X-->x1})|nil)}, {x0, x1 = 2}) --> {by [var-var binding]} ({runnable(nil)}, {x0 = 2, x1 = 2}) ------------------------------------------ Q: Is this final state terminal? Yes Can think of the output of this as the state of some particular location 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 We calculate as follows... ({runnable((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, {})|nil)}, {}) --> {by [local]} ({runnable((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, {X-->x0})|nil)}, {x0}) --> {by [local]} ({runnable((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, {X-->x0, Y-->x1})|nil)}, {x0, x1}) --> {by [sequence]} ({runnable((Y = proc {$ R} R = X end, {X-->x0, Y-->x1}) |(X = true, {X-->x0, Y-->x1}) |(local X in X = false local Z in {Y Z} if Z then skip else Z = X end end end, {X-->x0, Y-->x1})|nil)}, {x0, x1}) --> {by [value-creation]} ({runnable((X = true, {X-->x0, Y-->x1}) |(local X in X = false local Z in {Y Z} if Z then skip else Z = X end end end, {X-->x0, Y-->x1})|nil)}, {x0, x1 = closure(proc {$ R} R = X end {X-->x0, Y-->x1})}) --> {by [value-creation]} ({runnable((local X in X = false local Z in {Y Z} if Z then skip else Z = X end end end, {X-->x0, Y-->x1})|nil)}, {x0 = true, x1 = closure(proc {$ R} R = X end {X-->x0, Y-->x1})}) --> {by [local]} ({runnable((X = false local Z in {Y Z} if Z then skip else Z = X end end, {X-->x2, Y-->x1})|nil)}, {x0 = true, x1 = closure(proc {$ R} R = X end {X-->x0, Y-->x1}), x2}) --> {by [sequence]} ({runnable((X = false, {X-->x2, Y-->x1}) |(local Z in {Y Z} if Z then skip else Z = X end end, {X-->x2, Y-->x1})|nil)}, {x0 = true, x1 = closure(proc {$ R} R = X end {X-->x0, Y-->x1}), x2}) --> {by [value-creation]} ({runnable((local Z in {Y Z} if Z then skip else Z = X end end, {X-->x2, Y-->x1})|nil)}, {x0 = true, x1 = closure(proc {$ R} R = X end {X-->x0, Y-->x1}), x2 = false}) --> {by [local]} ({runnable(({Y Z} if Z then skip else Z = X end, {X-->x2, Y-->x1, Z-->x3})|nil)}, {x0 = true, x1 = closure(proc {$ R} R = X end {X-->x0, Y-->x1}), x2 = false, x3}) --> {by [sequence]} ({runnable(({Y Z}, {X-->x2, Y-->x1, Z-->x3}) |(if Z then skip else Z = X end, {X-->x2, Y-->x1, Z-->x3})|nil)}, {x0 = true, x1 = closure(proc {$ R} R = X end {X-->x0, Y-->x1}), x2 = false, x3}) --> {by [application]} ({runnable((R = X, {X-->x0, Y-->x1, R-->x3}) |(if Z then skip else Z = X end, {X-->x2, Y-->x1, Z-->x3})|nil)}, {x0 = true, x1 = closure(proc {$ R} R = X end {X-->x0, Y-->x1}), x2 = false, x3}) --> {by [var-var binding]} ({runnable((if Z then skip else Z = X end, {X-->x2, Y-->x1, Z-->x3})|nil)}, {x0 = true, x1 = closure(proc {$ R} R = X end {X-->x0, Y-->x1}), x2 = false, x3 = true}) --> {by [if-true]} ({runnable((skip, {X-->x2, Y-->x1, Z-->x3})|nil)}, {x0 = true, x1 = closure(proc {$ R} R = X end {X-->x0, Y-->x1}), x2 = false, x3 = true}) --> {by [skip]} ({runnable(nil)}, {x0 = true, x1 = closure(proc {$ R} R = X end {X-->x0, Y-->x1}), x2 = false, x3 = true}) ------------------------------------------ Emphasize the application step and how it treats environments. If you want another example of applications, try local Res in local K in K = proc {$ X ?R} R=proc {$ Y ?Z} Z=X end end local F in local Three in Three = 3 {K Three F} local Four in Four = 4 {F Four Res} end end end end end (See the reducer's ReducerTest.oz output) Or do a case example... **** Memory management (2.5) ***** Memory organization (extra topic) Overall memory layout Q: How do we represent the environment and the store in a single address space on a conventional computer? One (standard way): ----------------- | Code | |---------------| | Static Data | | (constants) | |---------------| | run-time | ~ Environment | stack of ARs | | | | | | | v | \\\\\\\\\\\\\\\\| |\\\\\\\\\\\\\\\| | (heap) | ~ Store ----------------- Conventions: low address (0) at top, high at bottom of drawing stacks grow down (to bottom) Stack organization AR = activation record, one for each procedure (& function) call Q: Why is an activation record needed for every *call* of a procedure, instead of one for each procedure? So local identifiers, in particular the formals, denote values for each call Q: How to access the values of local identifiers in the environment? Use a 2-coordinate system: (lexical depth, offset) The depth is how many surrounding scope boundaries you have to cross to find the identifier's denotation, the offset is a local numbering within a scope How to access the locations when you know the (depth, offset)? (a) have a pointer (EP) to the current procedure's locals (an AR) (b) use offsets from EP to address named locals for the current call (c) use a pointer (static link, SL) to the AR for the surrounding environment (d) use offsets for the locals in surrounding environment (e) follow the right number of static links to get out to the right AR, and then use an offset info needed for a single execution of a procedure --------------------- Aho and Ullman's design for Activation Record (using static links): __________________________ RET: | returned value | | (for functions) | |________________________| PAR: | | | actual parameters | | | |________________________| | DL: | dynamic link | | |________________________| | SL: | static link | fixed | | (or display) | size | |________________________| fields | IP: | saved machine status | <_________ EP (env pointer) | | (ip and other regs) | |________________________| VAR: | local data | | (storage for vars) | |________________________| | | TEMP:| temporaries | | | |\\\\\\\\\\\\\\\\\\\\\\\\\ <____ SP (stack pointer) --------------------- dynamic link (DL): pointer to AR of caller, used in dynamic scoping optimization: parameters and results may be passed in machine registers Q: does saved part save information about caller or callee? usually (Sebesta, Aho, et al): stores information for caller less code why? -caller can access it on return, with less indirection -want to put as much in caller as possible, since callee's code is executed for all calls Q: How would this be used in making a call? 1. caller allocates space for return value and evaluates actuals 2. Caller saves status a. Caller stores return address, registers in IP b. Caller stores value of EP in callee's AR (in DL field) 3. Making the call a. The caller fills in the new value of the static link (or display) b. The caller allocates rest of fixed part of callee's AR c. The caller sets EP to new value d. jump to start of callee's code 4. Callee allocates space for local data Q: How would this be used in a return? 1. Callee copies return value into allocated space 2. Callee deallocates its AR (by setting SP) 3. Restore caller's context a. Callee loads EP from it's DL b. jump to resumption address (saved IP in caller's AR) c. caller restores saved state from IP field (registers) ***** Last call optimization (2.5.1) Q: What is a last call optimization? avoid creating an AR for the last call, and reuse the current one ------------------------------------------ % Fully recursive fun {Length Lst} case Lst of _|T then 1+{Length T} else 0 end end Tracing this: {Length 1|2|3|4|nil} fun {Length Lst} {LengthIter Lst 0} end % Tail recursive fun {LengthIter Lst N} case Lst of _|T then {LengthIter T N+1} else N end end Tracing this: {Length 1|2|3|4|nil} ------------------------------------------ ... {Length 1|2|3|4|nil} = 1 + {Length 2|3|4|nil} = 1 + (1 + {Length 3|4|nil}) = 1 + (1 + (1 + {Length 4|nil})) = 1 + (1 + (1 + (1 + {Length nil}))) = 1 + (1 + (1 + (1 + 0))) = 1 + (1 + (1 + 1)) = 1 + (1 + 2) = 1 + 3 = 4 ... {Length 1|2|3|4|nil} = {LengthIter 1|2|3|4|nil 0} = {LengthIter 2|3|4|nil 1} = {LengthIter 3|4|nil 2} = {LengthIter 4|nil 3} = {LengthIter nil 4} = 4 Q: What is it useful for? recursions, especially those that are tail recursive Q: Does the semantics already to this? yes Q: Do C, C++, and Java require this optimization? no Q: What does that say about using recursion in these languages? it may be less space/time efficient ***** Garbage collection (2.5.2-4) Q: Why is garbage collection useful? because humans are so bad at deallocation why? because it's a global analysis and so there are more details to keep track of than fit in our short-term memory (Does the semantics already allow it?) Not by the rules, but okay to add such a rule, although that could make the semantics nondeterministic (but it could be part of the output function) Q: Does C do garbage collection? C++? Java? Q: What kinds of error does garbage collection prevent? ------------------------------------------ KINDS OF ERRORS GC PREVENTS? ------------------------------------------ dangling references, where you point to something freed Q: Does garbage collection prevent memory leaks? It helps, but doesn't prevent it