CS 334
|
Distinction between what something does and how it does it.
Interested in supporting abstraction (separation between what and how).
Originally, designers attempted to create languages w/ all types and statements that were necessary.
Realized quickly that needed extensible languages.
First abstractions for statements and expressions - Procedures and Functions
Arrays and records, then pointers introduced to build new types and operations on them.
Built-in types have associated operations - representation is hidden (for most part)
Support of ADT's is most important innovation of 1970's.
Simula 67 - package op's w/ data types - representation not hidden
Clu, Mesa, Modula-2, Ada, Smalltalk
Come back to them in Chapter 9.
Iterators correspond to abstraction over control structure
- high-order fcns in ML even more so!
More support for abstraction, generally more expressive is language.
Use of parameters supports abstraction -
Creates more flexible program phrases.
Common, Global variables (in block-structured languages),
Parameters - data, subprograms, types
Access via indirection.
What if parameter is expression or constant? CHGTO4(2).
value (in), result (out), value-result (in-out)
result and value-result parameters must be variables, value can be any storable value.
Can be expensive for large parameters.
Ex.
Procedure swap(a, b : integer); var temp : integer; begin temp := a; a := b; b := temp end;Won't always work, e.g.
swap(i, a[i]) with i = 1, a[1] = 3, a[3] = 17.
No way to define a correct swap in Algol-60!
Expressive power - Jensen's device:
To compute: x = Sum for i=1 to n of Vi
real procedure SUM (k, lower, upper, ak); value lower, upper; integer k, lower, upper; real ak; begin real s; s := 0; for k := lower step 1 until upper do s := s + ak; sum := s end;
What is result of sum(i, 1, m, A[i])?
What about sum(i, 1, m, sum(j, 1, n, B[i,j]))?
If evaluating parameters has side-effects (e.g., read), then must know how and how many times parameter is evaluated to predict what will happen.
Therefore try to avoid call-by-name with expressions with side-effects.
Lazy evaluation is efficient implementation of call-by-name where only evaluate parameter once. Requires that there be no side-effects, since owise get diff. results.
Implement call-by-name using thunks - procedures which evaluate expressions - difficult and slow. Must pass around code for evaluating expression (including environment defined in). Can use the same THUNK's as show up in environment based interpreter.
Note different from call-by-text (which would allow capture of free vbles).
An (explicit) polymorphic function is one which takes a type parameter, which specializes it into a function of particular type.
Write f: forall t. T, if f takes a type parameter U
and returns a value of type T[U/t].
Use Fn t => (rather than fn x =>) to indicate
the parameter is a type.
Thus "id", defined by:
fun id t (x:t) = xI.e., id T is function of type T -> T.
Think of each polymorphic functions as representing a class of functions, each of which has a uniform (parameterized) type (and definition).
Notice that polymorphic functions are typically most useful when used with parameterized data types. Thus in ML, there are lots of polymorphic functions used in relation to elements of type 'a list or parameterized datatype definitions like trees and stacks.
Clu, Ada, Eiffel, Modula-3, C++ all support explicit parametric polymorphism.
No type inference. If have polymorphic function, must explicitly pass type parameter.
E.g.,
fun map t u (f: t -> u) (l: t list): u list
Apply it:
map string int length ["a","help","Willy"] = [1,4,5]
Write type as
forall t. forall u. (t -> u) -> (t list) -> u list
Makes clear that t, u are type variables.
Can understand implicit polymorphic terms as abbreviations for explicit polymorphic terms. Compare with type of map in ML - really same, but printed without universal quantifier.
Restrictions on polymorphism for ML.
Polymorphic functions can be defined at top-level or in let clauses, but polymorphic function expressions cannot be used as arguments of functions.
E.g.,
let fun id x = x in (id "ab", id 17) end;is fine, but can't write
let fun test g = (g [], g "ab") in test (fn x => x) end;In fact, can't even write:
fun test2 f = (f [], f 17);Gets confused since f is used with two different typings:
'a list -> 'b, int -> 'c and can't unify 'a list and int.
No problem writing this in explicit polymorphic language:
let fun test (g: forall t.t -> t): (int list, string) = (g (int list) [], g string "ab") in test (Fn t => fn (x:t) => x) end;
Explicit polymorphism most easily supported in languages with a reference semantics -- i.e., objects represented as reference to values.
Clu was one of the first polymorphic languages and it worked that way.
Ada in 1983 did not require this and so had different implementation strategies depending on whether or not the type was a reference or a value type. However, the type-checking was done statically. Basically, they needed to know at link time, what instantiations of types would be necessary.
Eiffel was one of the first object-oriented languages to support explicit polymorphism and did it correctly as all values are implicit references.
C++ implemented templates as a macro expansion hack. They didn't type check polymorphic classes until they were expanded. Among other things, templates have been a compilation nightmare as lots of copies could be made at load time and early compilers didn't do a good job of keeping track of what copies were already around.
Java is expected to add parametric polymorphism (as in the language GJ) within the next two years. While there may be some efficiency problems because of the desire to leave the underlying virtual machine alone, polymorphic classes will be type-checked at compile time.
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, methods, and blocks (e.g. try-catch blocks in Java.)
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:
How is procedure call made?
All storage (local and global) known at translation time (hence static).
Activation records can be associated with each code segment.
Structure:
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