We normally expect that if we change the names of formal parameters that it should not make any difference, but ...
Suppose we evaluate:
let fun g x y = x + y in g y end;(or in our language PCF:
(fn g => g y) (fn x => fn y => x+y))If we evaluate blindly we get:
fn y => y + yNotice that because of scoping, the actual parameter y has become captured by the formal parameter y!
We should get: fn w => y + w, which has a very different meaning!!
(Note that we did not run into this problem earlier since during our evaluations we never worked with terms with free variables - when going inside functions we replaced all formal parameters by the actual parameters, which didn't involve free variables).
A different order of evaluation would have brought forth the same problem, however.
We would like to have fn x => B to represent the same function as
fn y
=> B[x:=y] as long as y doesn't occur freely in B. (Called
alpha-conversion)
If you always alpha-convert to a new bound variable before substituting in, will never have problems.
Evaluate terms via environments:
Env = string -> values
An environment, rho, tells value of identifiers in term.
Write [[e]] rho for meaning of e with respect to environment rho.
E.g. if rho(x) = 12 and rho(y) = 2, then [[x+y]] rho = 14.
How does function application result in change of environment?
[[(fn x => body) actual]]rho = [[body]] rho [ [[actual]]rho / x]
where rho[v / x] is environment like rho except x has value "v".
This and rec are the only rules in which the environment changes!
Rest of rules look like the old interpreter (except identifiers looked up in environment)!
Replaces all uses of subst!
This means that computation no longer takes place by rewriting terms into new terms, interp is now a function from term to value.
Note that
let val x = arg in eis equivalent to
(fn x => e) argMust worry about scoping problems:
val test = let val x = 3; fun f y = x + y; val x = 12 in x + (f 7) end;What is value of test?
Change in scope is reflected by change in environment.
With functions must remember environment function was defined in!
When apply function, apply in defining environment.
test is equivalent to
(fn x => (fn f => ((fn x => x + (f 7)) 12) (fn y => x + y))) 3Then
[[(fn x => (fn f => ((fn x => x + (f 7)) 12) (fn y => x + y))) 3]] rho0 = [[(fn f => ((fn x => x + (f 7)) 12) (fn y => x + y)) ]] rho1 = [[(fn x => x + (f 7)) 12]] rho2 = [[x + (f 7)]] rho3 = 12 + ([[fn y => 3 + y]] rho1) 7 = 12 + [[3 + y]] rho4 = 12 + 3 + 7 = 22where rho0 is the starting environment and
rho1 = rho0 [ [[3]] rho0 / x] = rho0[ 3 / x] rho2 = rho1 [ [[fn y => x + y]] rho1 / f] <- Closure for f rho3 = rho2 [ [[12]] rho2 / x] = rho2[ 12 / x] rho4 = rho1 [ 7 / y]
Array [1..10, 'a'..'z'] of Real = Array [1..10] of Array ['a'..'z'] of Real
User fooled into thinking Array[A,B] of C is AxB->C, but really A->B->C.
Any discrete type as index.
No semi-dynamic arrays. Result of 2 principles:
1. All types must be determinable at compile time.
2. Array bounds are part of type.
Therefore, must have statically determinable array bounds.
3. Type of actual parameters must agree w/ type of formals
Therefore, no general sort routines, etc.
The major problem with Pascal
Procedure x(...; procedure y;...)Fixed in (new) ANSI standard.
:
y(a,2);
No checking if type of file read in matches what was originally written.
2. Problems w/ type compatibility
Assignment compatibility:
When is x := y legal? x : integer, y : 1..10? reverse?
What if type hex = 0..15; ounces = 0..15;
var x : hex; y : ounces;
Is x := y legal?
Original report said both sides must have identical types.
When are types identical?
Ex.:
Type T = Array [1..10] of Integer; Var A, B : Array [1..10] of Integer; C : Array [1..10] of Integer; D : T; E : T;Which variables have the same type?
--> A, B and D, E only.
Structural not always easy. Let
T1 = record a : integer; b : real end; T2 = record c : integer; d : real end; T3 = record b : real; a : integer end;Which are the same?
Worse:
T = record info : integer; next : ^T end; U = record info : integer; next : ^V end; V = record info : integer; next : ^U end;
Ada uses Name EquivalenceA
Pascal & Modula-2 use Name Equivalence for most part. Check!
Modula-3 uses Structural Equivalence
Two types are assignment compatible iff
e.g., type Boolean is (False, True)
Can overload values:
Color is (Red, Blue, Green) Mood is (Happy, Blue, Mellow)If ambiguous can qualify w/ type names:
Color(Blue), Mood(Blue)
i.e., Hex is range 0..15
Other attributes available to modify type definitions:
Accurate is digits 20 Money is delta 0.01 range 0.00 .. 1000.00 -- fixed pt!Can extract type attributes:
Hex'FIRST -> 1 Hex'LAST -> 15Can initialize variables in declaration:
declare k : integer := 0Arrays "Constrained" - semi-static like Pascal
type Two_D is array (1..10, 'a'..'z') of Realor "Unconstrained" (what we called semi-dynamic earlier)
type Real_Vec is array (INTEGER range <>) of REAL;Generalization of open array parameters of MODULA-2.
Of course, to use, must specify bounds,
declare x : Real_Vec (1..10)or, inside procedure:
Procedure sort (Y: in out Real_Vec; N: integer) is -- Y is open array parameter Temp1 : Real_Vec(1..N); -- depends on N Temp2 : Real_Vec (Y'FIRST..Y'LAST); -- depends on parameter Y begin for I in Y'FIRST ..Y'LAST loop ... end loop; ... end sort;Note Ada also has local blocks (like ALGOL 60)
All unconstrained types (w/ parameters) elaborated at block entry (semi-dynamic)
String type is predefined open array of chars:
array (POSITIVE range <>) of character;
Can take slice of 1-dim'l array.
E.g., if
Line : string(1..80)Then can write
Line(10..20) := ('a','b',.'c','d','e','f','g','h','i','j') -- gives assignment to sliceBecause of this structure assignment, can have constant arrays.
and dynamic properties - checked at run time
Example of dynamic are range, subscript, etc.
Specify dynamic properties by defining subtype. E.g.,
subtype digit is integer range 0..9;Subtypes also constrain parameterized array or variant record.
subtype short_vec is Real_Vec(1..3); subtype square_type is geometric (square)Subtypes do not define new type, add dynamic constraints.
Therefore can mix different subtypes of same type w/ no problems
Derived types define new types:
type Hex is new integer 0..15 type Ounces is new integer 0..15Now Hex, Ounces, and Integer are incompatible types: treated as distinct copies of 0..15
Can convert from one to other:
Hex(I), Integer(H), Hex(Integer(G))Derived types inherit operators and literals from parent type.
E.g., Hex gets 0,1,2,... +,-,*,...Use for private (opaque) types and when don't want mixing.
Helped by removing dynamic features from def of type subrange or index of array.
Can now have open array parameters (also introduced in ISO Pascal).
Variants fixed
Name equivalence in Ada to prevent mixing of different types. E.g., can't add Hex and Ounce.
Can define overloaded multiplication such that if
l:Length; w:Width;then l * w : Area.
Avoid second-class types.
Ex. in Pascal: Restrictions on return values of functions, lack of procedure variables, etc.
ML comes much closer to satisfying.
Modern tendency to strengthen static typing and avoid implicit holes in types system.
- usually explicit (dangerous ) means for bypassing types system, if desired
Try to push as many errors to compile time as possible by:
Important direction of current research in computer science:
Provide type safety, but increase flexibility.
Important progress over last 20 years:
Polymorphism, ADT's, Subtyping & other aspects of object-oriented languages.
What are storable values of language? Those that cannot be selectively updated.
Varies between languages.
Pascal: primitive (integer, real, char, boolean), sets, pointers
ML: primitive, records, tuples, lists, function abstractions, ref's to vbles.
Examine how variables allocated and lifetime.
E.g. Procedures, functions, and blocks (from ALGOL 60 & C, like parameterless procedures located in-line.)
Program unit represented during execution by unit instance, composed of code segment and activation record (gives info on parameters and local variables, and where to return after execution).
Activation Record Structure:
Return address
Access info on parameters
Space for local variables
Units often need access to non-local variables.
How is procedure call made?
2. Save state of caller (register, prog. counter).
3. Make sure callee knows how to find where to return to.
4. Enter callee at 1st instruction.
2. Caller restores state.
3. If fcn, make sure result value left in accessible location (register, on top of stack, etc.)
Static: E.g. FORTRAN and COBOL.
Stack-Based: E.g. ALGOL-like languages (including Pascal and C).
Dynamic: LISP, PROLOG, APL, ML, Miranda, Eiffel, etc. as well as aspects of Pascal, Ada, etc.
All storage (local and global) known at translation time (hence static).
Activation records can be associated with each code segment.
Structure:
Return address
Access info on parameters
Space for local variables
At compile time, both instructions and vbles can be accessed by
(unit name, offset)
At link time can resolve to absolute addresses.
Global info shared via common statement:
COMMON/NAME1/A,B,S(25)
Statement must occur in all units wishing to share information. Name of the block must be identical, though can give different names to variables. (Gives rise to holes in typing) Identifiers are matched in order w/ no checking of types across unit boundaries.
Space for all common blocks allocated and available globally.
Procedure call and return straightforward