Previous | Table of Contents | Next |
where I is the name of the instance, E is a list of interfaces exported by I, and A1, ...,An is a list of actual interfaces to which the formal imports of G are bound. EXPORTS E can be omitted, in which case it defaults to EXPORTS I. The instance I is equivalent to an ordinary module defined as follows:
MODULE I EXPORTS E; IMPORT A1 AS F1, ..., An AS Fn; Body I.
Notice that the generic module itself has no exports; they are supplied only when it is instantiated.
For example, here is a generic stack package:
GENERIC INTERFACE Stack(Elem); (* where Elem.T is not an open array type. *) TYPE T <: REFANY; PROCEDURE Create(): T; PROCEDURE Push(VAR s: T; x: Elem.T); PROCEDURE Pop(VAR s: T): Elem.T; END Stack. GENERIC MODULE Stack(Elem); REVEAL T = BRANDED OBJECT n: INTEGER; a: REF ARRAY OF Elem.T END; PROCEDURE Create(): T = BEGIN RETURN NEW(T, n := 0, a := NIL) END Create; PROCEDURE Push(VAR s: T; x: Elem.T) = BEGIN IF s.a = NIL THEN s.a := NEW(REF ARRAY OF Elem.T, 5) ELSIF s.n > LAST(s.a^) THEN WITH temp = NEW(REF ARRAY OF Elem.T, 2 * NUMBER(s.a^)) DO FOR i := 0 TO LAST(s.a^) DO temp[i] := s.a[i] END; s.a := temp END END; s.a[s.n] := x; INC(s.n) END Push; PROCEDURE Pop(VAR s: T): Elem.T = BEGIN DEC(s.n); RETURN s.a[s.n] END Pop; BEGIN END Stack.
To instantiate these generics to produce stacks of integers, you would use this:
INTERFACE Integer; TYPE T = INTEGER; END Integer. INTERFACE IntStack = Stack(Integer) END IntStack. MODULE IntStack = Stack(Integer) END IntStack.
Implementations are not expected to share code between different instances of a generic module because this will not be possible in general.
Implementations are not required to type-check uninstantiated generics, but they must type-check their instances. For example, if you made the following mistake:
INTERFACE String; TYPE T = ARRAY OF CHAR; END String. INTERFACE StringStack = Stack(String) END StringStack. MODULE StringStack = Stack(String) END StringStack.
everything would go well until the last line, when the compiler would attempt to compile a version of Stack in which the element type was an open array. It would then complain that the NEW call in Push does not have enough parameters.
The order of execution of the modules in a program is constrained by the following rule:
If module M depends on module N and N does not depend on M, then Ns body will be executed before Ms body, where
Except for this constraint, the order of execution is implementation dependent.
The keyword UNSAFE can precede the declaration of any interface or module to indicate that it is unsafethat is, uses the unsafe features of the language. An interface or module not explicitly labeled UNSAFE is called safe.
An interface is intrinsically safe if there is no way to produce an unchecked runtime error by using the interface in a safe module. If all modules that export a safe interface are safe, the compiler guarantees the intrinsic safety of the interface. If any of the modules that export a safe interface are unsafe, it is the programmer, rather than the compiler, who makes the guarantee.
It is a static error for a safe interface to import an unsafe one or for a safe module to import or export an unsafe interface.
The rules of logical syntax must follow of themselves, if we only know how every single sign signifies.
Ludwig Wittgenstein
An expression prescribes a computation that produces a value or variable. Syntactically, an expression is either an operand or an operation applied to arguments, which are themselves expressions. Operands are identifiers, literals, or types. An expression is evaluated by recursively evaluating its arguments and performing the operation. The order of argument evaluation is undefined for all operations except AND and OR.
To describe the argument and result types of operations, we use a notation such as procedure signatures. Because most operations are too general to be described by a true procedure signature, we extend the notation in several ways.
The argument to an operation can be required to have a type in a particular class, such as an ordinal type, set type, and so on. In this case, the formal specifies a type class instead of a type:
ORD (x: Ordinal): INTEGER
The formal type Any specifies an argument of any type.
A single operation name can be overloaded, which means that it denotes more than one operation. In this case, we write a separate signature for each of the operations:
ABS (x: INTEGER) : INTEGER (x: Float) : Float
The particular operation will be selected so that each actual argument type is a subtype of the corresponding formal type or a member of the corresponding formal type class.
The argument to an operation can be an expression denoting a type. In this case, we write Type as the argument type:
BYTESIZE (T: Type): CARDINAL
The result type of an operation can depend on its argument values (although the result type can always be determined statically). In this case, the expression for the result type contains the appropriate arguments:
FIRST (T: FixedArrayType): IndexType(T)
IndexType(T) denotes the index type of the array type T and IndexType(a) denotes the index type of the array a. The definitions of ElemType(T) and ElemType(a) are similar.
Previous | Table of Contents | Next |