In most cases, I'll try to make my point by example and then justification (if there is one), rather than by a long winded description of how things ought to look. In a few cases, I'll describe in detail the use of some of the more obscure (but useful) Modula-3 mechanisms, specifically opacity and partial revelation.
CONST
NumberOfStars = 100;
VAR
INTEGER allGoodBoysGoToHeaven:
PROCEDURE GoodProcedure(a: INTEGER; theCat: CARDINAL) : INTEGER;
Specifically,
Interface names are little trickier, since there may not be a one to one correspondence between module names and interface names (as is the case when there is more than one interface to a single file). Suppose there is a module Disk, then some interfaces we might have are:
(* * Copyright 1994, 1995 University of Washington * All rights reserved. * See COPYRIGHT file for a full description * * DESCRIPTION * This file does this... * * HISTORY * 03-Aug-95 Joe Someone (someone) at the University of Washington * Created *)
The HISTORY component is important because that is what whist uses to timestamp updates. If you change a file, you must whist it indicating what changed.
INTERFACE Widget; CONST Brand = "Widget-1.0"; ... END Widget.
INTERFACE Screen; TYPE T = OBJECT x,y: INTEGER; METHODS writeChar(x,y: INTEGER; t: TEXT); END; END Screen.
If you only want to identify an object as having an identifiable type with no fields or methods, then define it as a subtype of ROOT (for objects) or REFANY (for reference types).
INTERFACE I; TYPE T <: ROOT; TYPE T1 <: REFANY; END I.
Note that you REFANY is the only legitimate supertype for a REF type. Object types can have any ancestral relationship you like.
Within a scope where you wish to reveal the internal representatio of (say) T, you would need:
REVEAL T = ROOT OBJECT ...In this case, the specification of the supertype ROOT is unnecessary, since ROOT is a supertype of all objects. However, this simple example makes it easier to understand the more common use of types and revelations.
Usually, we want to be more restrictive about the opaque types within an interface. The basic approach is to define a public supertype of the type you wish to make opaque:
INTERFACE I;
TYPE SomeVisibleAncestorType = OBJECT
x: INTEGER;
END;
TYPE T <: SomeVisibleAncestorType;
And to elsewhere reveal the representation of T as:
MODULE I;
REVEAL T = SomeVisibleAncestorType BRANDED OBJECT
y: INTEGER;
END I.
Clients of interface I can access the field "x" of instances of type T,
which is a subtype of SomeVisibleAncestorType.
In effect, this approach says "SomeVisibleAncestorType" is a prefix of "T" and that some additional fields are revealed in T's suffix.
It is possible to leave room in both the prefix and suffix of T by declaring a placeholder type that occupies the part of T between the prefix and the suffix. Revelations later describe the contents of both.
INTERFACE I;
TYPE T1 <: ROOT; (* establish a prefix of T2 and T3 *)
TYPE T2 = T1 OBJECT (* define what goes between T2 and T3 *)
y: INTEGER;
END;
TYPE T3 <: T2; (* establish a suffix of T3 *)
Now, we can reveal information about T1, and T3 in separate places:
MODULE I;
REVEAL T1 = BRANDED OBJECT
x: INTEGER;
END;
REVEAL T3 = T2 BRANDED OBJECT
z: INTEGER;
END;
What we're saying in the interface is: "we've got this thing called T1
that has some fields in it, but I won't tell you what. But, I will
tell you about this thing called T2 that has a 'y' in it. There's
also a T3, which has a T2 in it, but I won't tell you anything else.
Then, in the implementation module, we announce that T1 has an x in
it, and T3 has a z in it. All T2's have a y, as do all T3's.
Do not use the capitalized forms LOOPHOLE, UNSAFE, or UNTRACED, or ADR in comments in your programs. Using a variant with some lowercase letters in it makes it easier to scan a large program for unsafe constructs.