CS 334
Programming Languages
Spring 2002

Lecture 12

ABSTRACTION

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.

Accessing non-local information:

Common, Global variables (in block-structured languages),

Parameters - data, subprograms, types

Data Parameters

1. Call by Reference (FORTRAN, Pascal):

Pass address of actual parameter.

Access via indirection.

What if parameter is expression or constant? CHGTO4(2).

2. Call by Copy (Algol 60, Pascal, C, etc.):

Actual parameter copies value to formal parameter (and/or vice-versa).

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.

3. Call by Name (Algol-60)

Actual parameter provides expression to formal parameter - re-evaluated whenever accessed.

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).

Type Parameters and Explicit Polymorphism

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) = x
I.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.

Explicit polymorphic terms more expressive!

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.

We will talk later about how Clu, Ada, Eiffel, and Java provide extra information about type parameters so that type-checking can occur statically.

STORAGE

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.

Program Units:

Separate segments of code - usually allow separate declaration of local variables.

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:

Units often need access to non-local variables.

How is procedure call made?

To call:

  1. Make parameters available to callee.

  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.

To return:

  1. Get return address and transfer execution to that point.

  2. Caller restores state.

  3. If fcn, make sure result value left in accessible location (register, on top of stack, etc.)

Memory allocation

Three types of languages:

Static using FORTRAN as example

Units: Main program, Subroutines, and Functions.

All storage (local and global) known at translation time (hence static).

Activation records can be associated with each code segment.

Structure:

At compile time, both instructions and vbles can be accessed by 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


Back to:
  • CS 334 home page
  • Kim Bruce's home page
  • CS Department home page
  • kim@cs.williams.edu