![]() Princeton University |
Computer Science 441 |
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]
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?
var x : integer {bound at translation time}The variable can only hold values of that type. (Pascal/Modula-2/C, etc.)
FORTRAN has implicit declaration using naming conventions
In either case, run real danger of problems due to typos.
Example in ML, if
datatype Stack ::= Nil | Push of int;then define
fun f Push 7 = ...What error occurs?
Answer: Push is taken as a parameter name, not a constructor.
Therefore f is given type: A -> int -> B rather than the
expected: Stack -> B
Dynamic: Variables typically do not have a declared type. Type of value may vary during run-time. Esp. useful w/ heterogeneous lists, etc. (LISP/SCHEME).
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.
Subranges Guard against errors, save space. (only for discrete types)
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?