1. More ADT's
      1. Modula-2
      2. Clu (1974)
      3. ML
  2. Type Systems
Skip [[section]]8.8 in text

Note that some items actually talked about in Lecture 14 are in the lecture 13 notes and are not repeated here!

More ADT's

Can also have package where user can manipulate objects of type stack (external representation) like in Modula-2.

Advantage to external rep. is that can define array of stack, etc.

	length : Natural := 100;
	type element is private; -- generic parameters
package stack is
	type stack is private;
	procedure make_empty (S : out stack);
	procedure push (S : in out stack; X : in element);
	procedure pop (S : in out stack; X: out element);
	function empty (S : stack)  return boolean;
	stack_error : exception;

	type stack is record
		space	: array(1..length) of element;
		top	: integer range 0..length := 0;
	end record;
end stack;

package body stack is

	procedure make_empty (S : out stack);
		S.top := 0;
	end make_empty ;

	procedure push (S : in out stack; X : element) is
		if full(S)  then 
			raise stack_error;
			S.top := S.top + 1;
			S.space(S.top) := X;
		end if;
	end push;

	procedure pop (S : in out stack; X: out element) is
		if empty(S) then 
			raise stack_error;
			X := S.space(S.top);
			S.top := S.top - 1;
		end if;
	end pop;

	function empty(S : in out stack) return boolean is
		return (top = 0);

	function full(S : in out stack) return boolean is
		return (S.top = length);

end stack;

Biggest problem is exposure of representation of ADT in private part of package spec.

Not available to user, but must recompile if change representation.


Modula (modular language) designed by Wirth in 1975 for programming small real-time control systems (no files, sets, or pointers)

Modula 2 is 1980 revision (influenced by Mesa at Xerox PARC) intended to synthesize systems programming with general purpose language supporting modern software engineering.

(operating system for Lilith microcomputer written in Modula 2)

  1. Minor changes to Pascal which simplify programming (reliability) and improve program readability and efficiency.

  2. Upward extension of Pascal to support large system design projects. (Modules which can be separately compiled and yet are type checked.)

  3. Downward extension to allow machine-level programming and supports coroutines.

Sample program

IMPORT element FROM elementMod;
	TYPE stack;
	PROCEDURE make_empty (VAR S : stack);
	PROCEDURE push (VAR S : stack; X : element);
	PROCEDURE pop (VAR S : stack; X: element);
	PROCEDURE empty (S : stack): BOOLEAN;

END stackMod.

		space	: array[1..length] of element;
		top	: INTEGER;

	PROCEDURE make_empty (VAR S : stack);
		S^.top := 0;
	END make_empty ;

END stackMod;

Opaque types (those declared without definition in Definition module) must be pointers or take no more space than pointers.

Compare and contrast Modula-2 and Ada on supporting abstract data types.

Representations fairly similar - both can import from other units (modules or packages) and export items to other units.

For external representations not much difference.

Private types in Ada vs opaque types in Modula.

Opaque types require Pointer (or other data type of same length or less)

-Use of opaque types does not force recompilation of user programs if representation changes - does force recompilation in Ada.

Internal representations of ADT's almost identical.

Ada more flexible via generic routines - can parameterize over types as well as sizes of objects. Can also use this to create new instances of packages - especially helpful for internal representations of data types.

Clu (1974)

Cluster is basis of support for abstract data types.

Provides both packaging and hiding of representation

(cvt used to go back and forth btn external abstract type and internal representation).

May have parameterized clusters where specify needed properties of type paramenter. E.g.,

	sorted_bag = cluster [t : type] is create, insert, ...
			where t has
				lt, equal : proctype (t,t) returns (bool);
Abstraction facilities described in Liskov et al. paper in collection on reserve shelf.

Biggest difference from Ada and Modula-2 is that cluster is a type. Therefore can create multiple copies. Elements of cluster types are held as implicit references.


ADT supported in very straightforward way in ML.

Provides for encapsulation and information hiding


abstype intstack = mkstack of (int list)
  with exception stackUnderflow
    val emptyStk = mkstack []
    fun push (e:int) (mkstack(s):intstack) = mkstack(e::s)
    fun pop (mkstack([])) = raise stackUnderflow
      | pop (mkstack(e::s)) = mkstack(s)
    fun top (mkstack([])) = raise stackUnderflow
      | top (mkstack(e::s)) = e
    fun IsEmpty (mkstack([])) = true
      | IsEmpty (mkstack(e::s)) = false

Generic stacks ADT:

abstype 'a stack = mkstack of ('a list)
  with exception stackUnderflow
    val emptyStk : 'a stack = mkstack []
    fun push (e:'a) (mkstack(s):'a stack) = mkstack(e::s)
    fun pop (mkstack([]):'a stack) = raise stackUnderflow
      | pop (mkstack(e::s):'a stack) = mkstack(s):'a stack
    fun top (mkstack([]):'a stack) = raise stackUnderflow
      | top (mkstack(e::s):'a stack) = e
    fun IsEmpty (mkstack([]):'a stack) = true
      | IsEmpty (mkstack(e::s):'a stack) = false

Cannot get at representation of stack

Reference to mkstack(l) will generate an error message.

Only access through constants and op's.

More sophisticated support through modules, which also support separate compilation.
See Ullman text or Harper's notes.

Type Systems

Languages like Pascal, C, Modula-2, etc. have monomorphic type systems. Each legal expression has exactly one type.

In Pascal get extreme position that procedures only take arrays w/exactly same dimensions as well as types.

Modula-2 starts opening up, by allowing different sizes of arrays as parameters, but still require same base type.

Restrictive since sort procedures will work with any ordered type.

Rather work with "polymorphic" functions and procedures.

Two main flavors of polymorphism: ad hoc and parametric (talk about subtype later).

Ad hoc also known as overloading. Done well it can make code easier to understand. E.g., "+" for real and integer addition.

Done poorly it can confuse and lead to errors, e.g. "+" for string concatenation.

Most languages provide some overloading (e.g., arith. operators, I/O), a few (e.g., Ada and C++) allow user to define overloaded operations (either infix or prefix).

E.g. in Ada, write

function "+"(m,n:Complex) return Complex is begin ... end;

Must provide a mechanism to support resolution of overloading - i.e. which actual operation does an instance refer to.

Two major ways of resolving:

  1. Context-independent: Resolution based solely on type of arguments (Pascal, ML, C++). E.g., m + n returns complex only if m, n are both complex.

  2. Context-dependent: Resolution based on surrounding context of expression.
    E.g., in Ada can define: function "+"(m,n:real) return Complex is ... end;

    Then, in "c + (3.3 + 4.1)", inner addition will return complex if "c" is complex and will return real if "c" is real.

    Easy to write expressions which have more than one assigment of operations to overloaded operators. Termed "erroneous" and should generate error.

Overloading is not essential and is easily abused. Useful when matches expectations from external notation. Confusing and unhelpful otherwise.

Parametric polymorphism usually considered a good thing.

These are almost always related to parameterized types.

E.g., sorting with array of T where T is ordered

Push, pop with stack of T.

Search of Binary search tree of T.

In ML, direct support for polymorphic functions on lists (parameterized).

Can define parameterized types through datatype definitions.
E.g., datatype 'a stack = Empty | Push of 'a * ('a stack)

Languages support two different forms of parametric polymorphism:
implicit and explicit.

Differ on whether must supply type parameter.

ML is example of implicit polymorphism. Needn't supply type of any expression. System derives most general type, often polymorphic.
E.g., reverse: 'a list -> 'a list.

Very clever type inference algorithm invented independently by Hindley and Milner.
Collects all information about types of terms in expression (assigning a variable if no info) and then tries to "unify" all information so mutually consistent.


fun map f nil = nil
  | map f (hd::tl) = (f hd):: (map f tl)

map: 'a -> 'b -> 'c
'b = 'd list (since second arg is "nil" or "hd::tl")

f: 'a = 'd -> 'e (since "f hd" is defined)

'c = 'e list (since result is a list, and, in 2nd clause, first element is "f hd")

Therefore, map: ('d -> 'e) -> ('d list) -> ('e list)

Overloading does not interact well with implicit polymorphism because often don't have type info necessary to disambiguate expressions.

Some ML designers (e.g., Harper) feel that it would have been better to omit overloading of built-in functions.