Princeton University |
Computer Science 441 |
Variable has 6 components
Scope and Lifetime same in some languages - clearly different in some (FORTRAN)
N := N + 1First N refers to location (l-value), second to value (r-value).
Obtaining value of variable called dereferencing. (going from reference or location to value)
E.g., name can change (via call by reference parameter)
Aliasing:
Similarly assignment of variables (e.g., x := y) can be by copying or by sharing.
In copy semantics, target variable retains its location, but copies new value from source variable.
In sharing semantics, target variable gets location of source variable, so both share the same location (like objects in Java).
Constants have values, but no location.
One way of classifying a language is according to sorts of entities that can be bound to an identifier. Called denotable values.
In Pascal, objects which can be bound to id's and corresponding declarations are:
Static vs Dynamic
Scope is associated with the static text of program.
Can determine scope by looking at structure of program rather than execution path telling how got there.
May have holes in scope of variable
program ... var M : integer; .... procedure A ... var M : array [1..10] of real; begin ... end; begin ... end.Variable M declared in main program is not visible in procedure A, since new declaration of M "shades" old declaration.
Symbol table keeps track of which declarations are currently visible.
Think of symbol table as stack. When enter a new scope, new declarations are pushed on and can shade old ones. When exit scope, declarations arising from the scope are popped off.
In particular an occurrence of an identifier in a procedure may be associated with different variables at different times in the execution of the program.
Example:
program ... var A : integer; procedure Y(B: integer); begin ...; B := A + B; ... end; {Y} procedure Z(...); var A: integer; begin ...; Y(...); ... end; {Z} begin {main} ...; Z(...); ... end.Question: Which variable with name A is used when Y is called from Z?
Push and pop entries when enter and exit scopes at run-time.
For obvious reasons, dynamic scoping usually implies dynamic typing!
LISP and APL use dynamic scoping (though SCHEME has default of static)
In Pascal or C, when enter procedure any local variables are allocated and are then deallocated when exit. (Called dynamic allocation of variables).
In block-structured language (e.g., Pascal, C, Modula-2, etc.):
Use run-time stack to allocate space for local variables and parameters when enter a new unit (procedure, functions, etc.). Space is called an activation record.
Pop it off run-time stack when exit unit.
Note that a procedure may have several activation records on stack if called recursively.
Even without recursion may have several distinct variables on stack with same name!
When pointers are used, utilize another kind of memory, called "heap".
When do "new(p)" operation where p is of type pointer to T, sufficient memory is allocated from the heap to hold a value of type p and p is assigned the location of that memory. The value is accessed by writing p^ (in Pascal).
This memory does not follow the stack discipline. The lifetime of the heap-allocated memory is determined manually by "new" and "dispose" commands. Entering or exiting a scope has no impact on the allocation or deallocation of this memory.
Therefore in Pascal and C (for example) there are three kinds of memory: static (occupied by global variables), stack-based or automatic (occupied by parameters and local variables of procedures and functions), and heap-based or manually allocated.
In ML, everything comes off of heap. But automatically allocated when needed and deallocated (by garbage collector) when no way of accessing it. Java is similar.
More implementation details later.
If freeze at compilation, then program constant
const size = 100; doubleSize = 2 * size {called manifest constant}In other languages allow:
procedure ... (n : integer) is var x: constant integer := 3 * n - 2; {value bound & frozen on procedure entry} A: array[1..n] of real;Some language allow variables to be initialized at declaration.
var x : integer := 5;But when is binding done - only first time procedure is entered or every time ? FORTRAN only first time, Java every time.
Postpone discussion of binding time for types until later.
This can occur especially easily when using var parameters.
Ex: If p(x,y) is a procedure where x and y are both var parameters, then the call p(z,z) makes x and y aliases in the body of p.
If the body of p(x,y) first increases x by one and then y by one, what is the result of the call to p(z,z)? Get z increased by 2! Aliasing often producing surprising (undesirable) behavior in functions.
Also arises when global variable, x, is used inside procedure where x was used as an actual parameter for a var parameter.
Also easy to get aliasing with pointers:
var x, y: ^ int; ... x := y;Then x^ and y^ are aliases - any change to one, changes the other!
In languages with assignment by sharing (e.g., Java), get aliasing automatically with all assignments.
"Pointers have been lumped with the goto statement as a marvelous way to create impossible to understand programs."
Kernighan & Ritchie, The C Programming Language
(In fairness, they then go on to defend the use of pointers.)
S x T = {<s,t> | s in S , t in T}.Can also write as PRODi in I Si = S1 x S2 x ... x Sn. If all are the same, write Sn.
Tuples of ML: type point = int * int
How many elts in product?
What if have So? Called unit in ML.
Differ from Cartesian product since fields associated with labels
E.g.
record record x : integer; /= a : integer; y : real b : real end; end
Operations and relations: selection ".", :=, =.
Can use generalized product notation: PRODl in Lab T(l)
Ex. in first example above, Lab = {x,y}, T(x) = integer, T(y) = real.
Support alternatives w/in type:
Ex.
RECORD name : string; CASE status : (student, faculty) OF student: gpa : real; class : INTEGER; | faculty: rank : (Assis, Assoc, Prof); END; END;
Save space yet (hopefully) provide type security. Saves space because the amount of space reserved for a variable of this type is the larger of the variants.
Fails in Pascal / MODULA-2 since variants not protected.
How is this supported in ML?
datatype IntReal = INTEGER of int | REAL of real;Can think of enumerated types as variant w/ only tags!
NOTICE: Type safe. Clu and Ada also support type-safe case for variants:
Ada: Variants - declared as parameterized records:
type geometric (Kind: (Triangle, Square) := Square) is record color : ColorType := Red ; case Kind of when Triangle => pt1,pt2,pt3:Point; when Square => upperleft : Point; length : INTEGER range 1..100; end case; end record; ob1 : geometric -- default is Square ob2 : geometric(Triangle) -- frozen, can't be changedAvoids Pascal's problems w/holes in typing.
Illegal to change "discriminant" alone.
ob1 := ob2 -- OK ob2 := ob1 -- generate run-time check to ensure TriangleIf want to change discriminant, must assign values to all components of record:
ob1 := (Color=>Red,Kind=>Triangle,pt1=>a,pt2=>b,pt3=>c);
If write code
... ob1.length...then converted to run-time check:
if ob1.Kind = Square then ... ob1.length .... else raise constraint_error end if.
Fixes type insecurity of Pascal
Note disjoint union is not same as set-theoretic union, since have tags.
IntReal = {INTEGER} x int + {REAL} x real
C supports undiscriminated unions:
typedef union {int i; float r;} utype.As usual with C, it is presumed that the programmer knows what he/she is doing and no static or run-time checking is performed.
Mapping from index type to range type
E.g. Array [1..10] of Real corresponds to {1,...,10} -> Real
Operations and relations: selection ". [.]", :=, =, and occasionally slices.
E.g. A[2..6] represents an array composed of A[2] to A[6]
Index range and location where array stored can be bound at compile time, unit activation, or any time.
For instance, in Pascal, an array stored in a local variable is allocated on the run-time stack, and its location on the stack may vary in different invocations of the procedure.
With semi-dynamic (or dynamic) arrays, the index set (and hence size) of the array may vary at run-time. For instance in ALGOL 60 or Ada, an array held in a local variables may have index bounds determined by a parameter to the routine. It is called semi-dynamic because the size is fixed once the routine has been activated.
A flexible array is one whose size can change at any time during the execution of a program. Thus, while a particular size array may be allocated when a procedure is invoked, the array may be expanded in the middle of a loop if more space is needed.
The key to these differences is binding time, as usual!
What is difference from an array? Efficiency, esp. w/update.
update f arg result x = if x = arg then result else f xor
update f arg result = fn x => if x = arg then result else f xProcedure can be treated as having type S -> unit for uniformity.
set of elt_type;Typically implemented as bitset or linked list of elts
Operations and relations: All typical set ops, :=, =, subset, .. in ..
Why need base set to be primitive type? What if base set records?
tree = Empty | Mktree of int * tree * treeIn most lang's built by programmer from pointer types.list = Nil | Cons of int * list
Sometimes supported by language (e.g. Miranda, Haskell, ML).
Why can't we have direct recursive types in ordinary imperative languages?
OK if use ref's:
list = POINTER TO RECORD first:integer; rest: list END;
Recursive types may have many sol'ns
E.g. list = {Nil} union (int x list) has following sol'ns:
Theoretical result: Recursive equations always have a least solution - though infinite set if real recursion.
Can get via finite approximation. I.e.,
list0 = {Nil}Very much like unwinding definition of recursive functionlist1 = {Nil} union (int x list0) = {Nil} union {(n, Nil) | n in int}
list2 = {Nil} union (int x list1) = {Nil} union {(n, Nil) | n in int} union {(m,(n, Nil)) | m, n in int}
...
list = Unionn listn
fact = fun n => if n = 0 then 1 else n * fact (n-1) fact0 = fun n => if n = 0 then 1 else undef fact1 = fun n => if n = 0 then 1 else n * fact0(n-1) = fun n => if n = 0, 1 then 1 else undef fact2 = fun n => if n = 0 then 1 else n * fact1(n-1) = fun n => if n = 0, 1 then 1 else if n = 2 then 2 else undef ... fact = Unionn factnNotice solution to T = A + (T->T) is inconsistent with classical mathematics!
datatype univ = Base of int | Func of (univ -> univ);
operations: hd, tail, cons, length, etc.
Persistent data - files.
Are strings primitive or composite?