CS 334
|
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, but this is a pain in the neck.
Instead we will valuate terms with respect to 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]
Dynamic more flexible, but more overhead since must check type before performing operations (therefore must store tag w/ value).
Dynamic binding found in APL and LISP.
no characters or strings, no user-defined of any sort.
Arrays - at most 3-dim'l of built-in type. Subscripts begin at 1
Orig., restricted form of subscript expressions.
No records or sets. Many holes in typing.
Arrays of built-in types - no limit on dim'n, bounds any integers, semi-dynamic arrays
No records or sets. Strongly and statically typed.
Array [1..10, 'a'..'z'] of Real = Array [1..10] of Array ['a'..'z'] of RealUser 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:
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 := 0
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.