There was a brief introduction to the concept of opaque types in section 10.7.3, and the reader should review that comment before continuing.

Here, the goal will be to implement an ADT in an opaque way, that is, with the structural details of the type deliberately hidden away in the implementation module so that clients cannot know what they look like.

Here, this will be achieved by revisiting an earlier example. In section 6.9, in the introduction to library modules, the ADT *Point* was implemented in a transparent manner, that is, with the implementation details available for all to see in the definition module.

TYPEPoint =ARRAY[1 .. 2]OFREAL;

Client programs "know" the names of the parts of the type *Point*. The way that they are written may depend heavily on this fact. It is a better idea in practice to keep such details hidden, so that the client program does not have access to such information, but rather require that such data items can be operated upon only by the procedures that are also contained in the module along with the data type definition. This has been referred to, and there have been several instances of predefined library modules containing such opaque types throughout the text.

Object oriented designconcentrates on the need for a type of data. The data type and all the operations on it are implemented in such a way that an item of that type can be modified only through those operations, and not by any client program.

There is somewhat more to object oriented design than this in practice, especially if one is working in a notation built from the ground up on such a philosophy. However, this is a good working start for such thinking.

Here is the revised definition of the ADT *Point* with some procedures changed from functions so that they can return *Point* in a variable parameter.

DEFINITIONMODULEOpaquePoints; (* based on an original design by R. Sutcliffe modified to opaque form by Gord Tisher last modification 1995 05 17 *) (* angles are measured in radians counterclockwise from the positive x-axis *)TYPEPoint;PROCEDUREnewPoint (x, y :REAL;VARp : Point); (* allocates memory for a new point *)PROCEDUREkillPoint (VARp : Point); (* deallocates memory the point variable -- using the variable after a call to killPoint is wrong. *)PROCEDUREassign (x, y :REAL;VARp : Point); (* returns the abstract point with coordinates x and y *)PROCEDUREabscissa (p : Point) :REAL; (* returns the first, or x-coordinate of the point *)PROCEDUREordinate (p : Point) :REAL; (* returns the second, or y-coordinate of the point *)PROCEDUREabs (p : Point) :REAL; (* returns the distance from the point to the origin *)PROCEDUREarg (p : Point) :REAL; (* returns the angle to the positive x-axis subtended by a line segment from the origin to the point measured in the range 0 to 2¼lt;¼gt; radians *)PROCEDUREpolarToRect (abs, arg :REAL;VARp : Point); (* returns the point with the given absolute value and argument *)PROCEDUREreflectX (VARp : Point); (* returns the reflection of the point in the x-axis *)PROCEDUREreflectY (VARp: Point ); (* returns the reflection of the point in the y-axis *)PROCEDUREreflect0 (VARp : Point); (* returns the reflection of the point in the origin *)PROCEDUREreflect45 (VARp : Point); (* returns the reflection of the point in the line y = x *)PROCEDUREscale (VARp : Point; scaleFactor :REAL); (* returns the point with the same argument as p and its absolute value multiplied by the scale factor *)PROCEDURErotate (VARp : Point; rotAngle :REAL); (* returns the point with the same absolute value as p and with its argument increased by rotAngle *)PROCEDUREtranslate (VARp : Point; deltaX, deltaY :REAL); (* returns the point obtained by shifting the given point deltaX horizontally and deltaY vertically *)ENDOpaquePoints.

It is time to clear up a little mystery regarding opaque types that may be troubling the thinking reader. Such types are named in the definition module, but their structure is not given there. How then can the compiler know how much memory to assign statically to such a variable? After all, the earliest that the details can become available is the time when the implementation module is linked to the client program.

In defining Modula-2, Wirth replied that the only possible course of action for the compiler in this case is to assume that opaque types are always a certain fixed size, never more nor less. He concluded that this in turn meant that the opaque type itself would in practice have to be a pointer, and that the data represented by the abstraction would have to be contained in the structure to which it points. Wirth left open the possibility that someone might find some other way to achieve this that would allow opaque types to be other than pointers, and some implementations took advantage of this to the extent that they allowed opaque types to be any type that occupied the same amount of memory as a pointer type. That is, this limitation was a practical one, but not a theoretical one. Although the restriction of opaque types to pointers is clearly an artifact of current technology, in the sense that the limitation results from the standard view of what must happen at compile-link-run time, the ISO standards committee took a much narrower view of the matter:

In an ISO standard conforming implementation, any opaque type defined in a definition module must be redefined in the corresponding implementation module either as another opaque type or as a pointer type.

Any redefinition in terms of another opaque type merely postpones the inevitable--an opaque type is, in the (ISO) end a pointer type. This can be clearly seen in the implementation of the *OpaquePoints* module below. The type Point is a pointer to the data structure that contains the coordinates of the point, and all access to the actual data must employ the dereferencing operator. Observe that the student who implemented the design chose to use ALLOCATE directly rather than NEW. Note, however, that ALLOCATE and DEALLOCATE appear only once in the entire module, confining their presence to as small a scope as possible, and ensuring good control over memory use.

IMPLEMENTATIONMODULEOpaquePoints; (* based on an original design by R. Sutcliffe modified to opaque form by Gord Tischer last modification 1995 05 18 *)FROMStorageIMPORTALLOCATE, DEALLOCATE;FROMRealMathIMPORTsqrt, arctan, sin, cos, pi;TYPEPoint =POINTERTOPointData; PointData =ARRAY[1..2]OFREAL;PROCEDUREnewPoint (x, y :REAL;VARp : Point);BEGINALLOCATE (p,SIZE(PointData)); p^[1] := x; p^[2] := y;ENDnewPoint;PROCEDUREkillPoint (VARp : Point); (* deallocates memory the point variable -- using the variable after a call to killPoint is wrong *)BEGINDEALLOCATE (p,SIZE(PointData));ENDkillPoint;PROCEDUREassign (x, y :REAL;VARp : Point);BEGINp^[1] := x; p^[2] := y;ENDassign;PROCEDUREabscissa (p : Point) :REAL;BEGINRETURNp^[1];ENDabscissa;PROCEDUREordinate (p : Point) :REAL;BEGINRETURNp^[2];ENDordinate;PROCEDUREabs (p : Point ) :REAL;BEGINRETURNsqrt (p^[1] * p^[1] + p^[2] * p^[2]);ENDabs;PROCEDUREarg (p : Point) :REAL; (* if both coordinates are zero, the angle is not defined, but this procedure will return zero. No errors are generated. *)VARtemp :REAL;BEGINIFp^[1] = 0.0THENIFp^[2] > 0.0 (* case of point on positive y-axis *)THENRETURNpi / 2.0;ELSIFp^[2] < 0.0THENRETURN1.5* pi (* case of point on negative y-axis *)ELSERETURN0.0;END;END; temp:= arctan (p^[2]/p^[1]); (* returns first and fourth quadrants only *)IFp^[1] > 0.0THENIFp^[2] >= 0.0THENRETURNtemp;ELSERETURN(2.0 * pi) + temp;END; (* if *)ELSERETURNpi + temp (* adjust for second and third quadrants *)END;ENDarg;PROCEDUREpolarToRect (abs, arg :REAL;VARp : Point);VARtemp : PointData;BEGINtemp[1] := abs * (cos (arg)); temp[2] := abs * (sin (arg)); p^ := temp;ENDpolarToRect;PROCEDUREreflectX (VARp : Point );BEGINp^[2] := -p^[2];ENDreflectX;PROCEDUREreflectY (VARp : Point );BEGINp^[1] := -p^[1];ENDreflectY;PROCEDUREreflect0 (VARp : Point );BEGINp^[1] := -p^[1]; p^[2] := -p^[2];ENDreflect0;PROCEDUREreflect45 (VARp : Point );VARtemp :REAL;BEGINtemp := p^[1]; p^[1] := p^[2]; p^[2] := temp;ENDreflect45;PROCEDUREscale (VARp : Point; scaleFactor :REAL);BEGINp^[1] := scaleFactor * p^[1]; p^[2] := scaleFactor * p^[2];ENDscale;PROCEDURErotate (VARp : Point; rotAngle :REAL);VARr, theta :REAL;BEGINr := abs (p); theta := arg (p) + rotAngle; polarToRect (r, theta, p);ENDrotate;PROCEDUREtranslate (VARp : Point; deltaX, deltaY :REAL);BEGINp^[1] := p^[1] + deltaX; p^[2] := p^[2] + deltaY;ENDtranslate;ENDOpaquePoints.

Observe that if the implementor had later decided to change the type *PointData* and implement it as a record instead, only this implementation module, and not any client program, would have to be re-compiled. Client programs would only have to be re-linked to take advantage of the new code. They need no re-coding themselves to do this, since their code does not depend on the structural details of the opaque type. Indeed, it cannot, for those details are deliberately concealed from the client programs.