type SCALAR is digits 8; type COLOR is (VIOLET, INDIGO, BLUE, GREEN, YELLOW, ORANGE, RED); package METRIC is type COORDINATE is record X : SCALAR := 0; Y : SCALAR := 0; Z : SCALAR := 0; end record; function "+" (LEFT, RIGHT : COORDINATE) return COORDINATE; function "-" (LEFT, RIGHT : COORDINATE) return COORDINATE; procedure INVERT(A : in out COORDINATE); end METRIC; |
By derivation we can create the following new types:
type MASS is new SCALAR; type LENGTH is new SCALAR; type AREA is new SCALAR; type DYE is new COLOR; type HUE is new COLOR; type POINT is new METRIC.COORDINATE; type FORCE is new METRIC.COORDINATE; type VECTOR is new METRIC.COORDINATE; |
The motivation for creating new types that are copies of existing types will be examined in later sections. For the time being let us review the properties of such types - obtained by derivation. In each case, the derived type is a copy of its parent type. This has several implications concerning the type class, the set of values, the applicable operations, and overloading.
The derived type belongs to the same class as its parent type. Thus DYE is an enumeration type since COLOR is an enumeration type; similarly, POINT is a record type since COORDINATE is a record type.
The set of values of the derived type is a copy of the set of values of its parent type. Thus we have a set of seven values for the type DYE - exactly as for the parent type COLOR. There is a one-to-one correspondence between the two sets of values; but these two sets are nevertheless distinct: it would not be possible to assign a value of type DYE to a variable of type COLOR.
The basic operations for the derived type are as for the parent type. For example, if component selection is available for the parent type, it is available for the derived type. Thus selection of the component Y (by dot notation) is available for the type POINT since it is available for the type COORDINATE; similarly aggregates exist for both types and they use the same notation.
Attributes are basic operations, so the previous rule applies: If an attribute is available for the parent type, it is available for the derived type. Thus the attribute FIRST is available for the type MASS since it is available for the type SCALAR: the attribute FIRST for the type MASS yields a value of type MASS; the attribute FIRST for the type SCALAR yields a value of type SCALAR. The (implicit) declarations of these two attributes are in fact as follows (this is NOT legal Ada):
function SCALAR'FIRST return SCALAR; -- for the type SCALAR function MASS'FIRST return MASS; -- for the type MASS |
Implicit conversions of numeric literals are also basic operations. Hence there exists an implicit conversion of any real literal (such as 1.54) to the type MASS since there exists such a conversion for the type SCALAR (the parent type of MASS).
If a given enumeration literal exists for the parent type, there is a corresponding enumeration literal - with the same identifier - for the derived type. Thus there is the enumeration literal INDIGO for the type DYE since there is an enumeration literal INDIGO for the type COLOR. The (implicit) declarations of these two enumeration literals are in fact as follows:
function INDIGO return COLOR; -- for the type COLOR function INDIGO return DYE; -- for the type DYE |
Thus each literal yields a value of the corresponding type: as we know already there is a correspondence between the indigo value of COLOR and that of DYE, but they are distinct values belonging to distinct types.
For each predefined operation of the parent type there is a corresponding predefined operation of the derived type. For example we have the addition:
function "+" (LEFT, RIGHT : SCALAR) return SCALAR;
for the type SCALAR and hence the corresponding additions for the derived types:
function "+" (LEFT, RIGHT : MASS) return MASS; -- for the type MASS function "+" (LEFT, RIGHT : LENGTH) return LENGTH; -- for the type LENGTH function "+" (LEFT, RIGHT : AREA) return AREA; -- for the type AREA |
For a type declared in the visible part of a package, each subprogram that has a parameter or result of the type and is declared within the visible part of this package is derivable. This means that corresponding operations are derived by the derived type. For example, the package METRIC defines an addition, a subtraction, and a procedure INVERT for the type COORDINATE; and hence the corresponding subprograms are derived for the type POINT:
function "+" (LEFT, RIGHT : POINT) return POINT; function "-" (LEFT, RIGHT : POINT) return POINT; procedure INVERT(A : in out POINT); |
Note that these derived operations are obtained by systematic substitution of the name of the derived type for the name of the parent type.
(We will say more about the effect of these derived subprograms after we have presented explicit conversions.)
The above description shows that a derived type is very much like its parent type. They are nevertheless distinct types. Thus with the declarations
C : COLOR := INDIGO; D : DYE := VIOLET; H : HUE := RED; |
assignments such as the following are illegal
D := H; -- Illegal: a hue value cannot be assigned to a dye C := D; -- Illegal: a dye value cannot be assigned to a color |
These assignments are not allowed because we are dealing with distinct types and distinct sets of values. However, there is a one-to-one correspondence between these sets of values and, for this reason, the language provides explicit conversions between corresponding values. For example
DYE(H)
is an explicit conversion of the value of H - of type HUE - into the corresponding value of type DYE: here it will yield the RED value of the type DYE, and so the following assignment is legal
D := DYE(H);
Type conversions between types that are derived directly or indirectly from each other (or from a common parent type) usually do not result in any run-time executable code. Such conversions are also involved (implicitly) in the derivation of a derivable operation. Consider for example the procedure INVERT:
procedure INVERT(A : in out COORDINATE) is begin A.X := - A.X; A.Y := - A.Y; A.Z := - A.Z; end; |
and the derivation of the procedure
procedure INVERT(A : in out POINT);
The effect of this derived operation is obtained by application of the parent procedure, but conversion of the parameter to the parent type is assumed to take place before the call, and conversion back to the derived type is assumed to take place after the call. Thus for a variable P of type POINT, the call
INVERT(P);
has the same effect as
declare use METRIC; K : COORDINATE; begin K := COORDINATE(P); -- convert to parent type METRIC.INVERT(K); -- call parent procedure P := POINT(K); -- convert back to derived type end; |
or simply
METRIC.INVERT(METRIC.COORDINATE(K));
but this form does not show the conversion back.
Here again these conversions usually do not result in any run-time executable code but they are needed to explain the use of the procedure METRIC.INVERT, which is only applicable to the type METRIC.COORDINATE.
A final point to consider with derived types is overloading. Derivation creates several overloaded entities. Thus we have
C := INDIGO; -- the INDIGO of the type COLOR D := INDIGO; -- the INDIGO of the type DYE |
Qualification can be used when the context is not sufficient for the determination of the meaning of an overloaded construct. For example the following comparison is ambiguous (and admittedly somewhat pathological):
if (X => A, Y => B, Z => C) = (X => U, Y => V, Z => W) then -- ambiguous
But this ambiguity can be resolved by qualification of one or of both aggregates:
POINT'(X => A, Y => B, Z => C) = (X => U, Y => V, Z => W)
or
POINT'(X => A, Y => B, Z => C) = POINT'(X => U, Y => V, Z => W)
or
(X => A, Y => B, Z => C) = POINT'(X => U, Y => V, Z => W)
We next review major classes of use of derived types.