[Ada Information Clearinghouse]
Ada '83 Rationale, Sec 4.4: Constraints and Subtypes

"Rationale for the Design of the
Ada® Programming Language"

[Ada '83 Rationale, HTML Version]

Copyright ©1986 owned by the United States Government. All rights reserved.
Direct inquiries to the Ada Information Clearinghouse at adainfo@sw-eng.falls-church.va.us.

CHAPTER 4: Types

4.4 Constraints and Subtypes

So far we have seen that a type characterizes a set of values that may be assumed by objects of the type, and a set of operations that may be applied to these values and objects. The fact that an object has a certain type is a static property of the program: it follows directly from the declaration of the object.

We shall now see how to restrict the values that may be assumed by an object to a subset of the values of the type. Such a restriction is called a constraint, and it does not affect the set of applicable operations. A subtype is a type together with an associated constraint. An object can be declared to have a certain subtype, and this is then a static property of the object. But in general it will not always be possible to determine statically (at compilation time) whether or not a value satisfies a constraint and thereby belongs to a corresponding subtype. Thus constraints and subtypes are concepts that are, in general, related to the dynamic behavior of programs.

In this section...

4.4.1 Constraints
4.4.2 Subtypes
4.4.3 Evaluation of Constraints

4.4.1 Constraints

A constraint can be used to restrict the set of values that may be assumed by an object of a given type, as in the following example:

    START :  DAY range MON .. WED;

Had we declared the variable START as

    START :  DAY;       -- only on MON, TUE, and WED

then all values of this type would be assignable to START - the comment notwithstanding. Given the constraint, however, the only assignable values are those in the range MON .. WED, that is, the values MON, TUE, and WED.

Constraints may be used effectively by compilers for optimization purposes. Their major purpose, however, is for greater program reliability: a constraint expresses a logical requirement on our program in an explicit manner, and it therefore opens up the possibility of reporting violations of this logical requirement, should they ever occur.

In principle these violations will be reported at execution time by raising the exception CONSTRAINT_ERROR. This means that, in general, compilers will generate code that dynamically checks constraint satisfaction. In practice however, compilers will be able to report certain potential constraint violations at compilation time. In other situations they will be in a position to omit a given check, since success has been guaranteed by a prior check.

Examples of assignments are given in the block statement below. The comment static check refers to situations where the check can be done at compilation time (in anticipation). The comment dynamic check refers to situations where a check at run time is actually required.

  TODAY  :  DAY;
  START  :  DAY     range MON .. WED;
  STOP   :  DAY     range MON .. FRI;
  MID    :  DAY     range WED .. THU;
  START  :=  TUE;        -- static check : since TUE is a literal
  STOP   :=  FRI;        -- static check : since FRI is a literal
  TODAY  :=  START;      -- static check : any value is allowed for TODAY
  STOP   :=  START;      -- static check : the range of STOP
                         --  includes that of START
  START  :=  STOP;       -- dynamic check : STOP <=  WED
  MID    :=  TODAY;      -- dynamic check : TODAY in WED .. THU
  STOP   :=  MID;        -- static check : the range of MID is
                         --  included in that of STOP

4.4.2 Subtypes

It is good programming practice to factor out the knowledge of common properties, and this applies to constraints as well. Assume for example that at several places in a program we find objects declared with a type and constraint such as

    DAY range MON .. FRI

Then it would be better to associate a name with this group and use this name for the corresponding object declarations. This can be achieved by a subtype declaration (a type name followed by a constraint is actually called a subtype indication):

    subtype WORKDAY is DAY range MON .. FRI;

where the name chosen for the subtype is carefully chosen to convey the intent.

The name of a subtype is an abbreviation for the associated type name and constraint. Thus a subtype declaration does not define a new type, and objects of different subtypes of a given type are compatible for assignment. In an expression, such objects can be used at any place where a value of the given type is expected; the constraint on an object need be checked only upon assignment to the object, as shown in the previous examples.

The advantages of using subtypes are the usual maintainability advantages of any factoring mechanism. For example, if we want to change the range of workdays, then a single textual change is needed, namely in the subtype declaration. Without named subtypes, it would be necessary to inspect all occurrences of the range MON .. FRI in the program, in order to detect those occurrences where the intent was to use this range for workdays.

We can also define hierarchies of subtypes by constraining other subtypes. Consider for example the type CHARACTER. In Ada this is a predefined enumeration type whose enumeration literals are character literals (such enumeration types are called character types). Now we can define a subtype such as

    subtype LETTER is CHARACTER range 'A' .. 'Z';

for upper-case letters. Subsequently we can define a subtype such as

    subtype LAST_11 is LETTER range 'O' .. 'Z';

For this to be correct, the range 'O' .. 'Z' must be compatible with that of LETTER, that is, it must be a subinterval of 'A' .. 'Z'. This is checked, and so an error such as writing the character '0' (the digit zero) instead of the upper-case letter 'O' would be detected - the character '0' (zero) does not belong to the range 'A ' .. 'Z'.

4.4.3 Evaluation of Constraints

All the examples presented so far included constraints that can be evaluated statically. Certain constraints that determine critical space requirements must be known at compilation time, since space optimization would not be possible in the case of dynamically computed values. For example, the range of an integer type had better be known statically, in order to allow the compiler to select the appropriate single-length or double-length machine instructions.

However, requiring static evaluation in every case would be much too restrictive. The assertions expressed by range constraints would be too coarse, ranges could not be used as general loop iteration ranges, and arrays could only be of static size. A balance must be struck in this respect, and the rules of Ada represent a deliberate choice of when evaluation must be static.

An issue to be considered is the time when the expressions appearing in constraints should be evaluated. Consider the subtype declaration:

    subtype PAST is DAY range MON .. TODAY;

where TODAY is a variable. The rule adopted in Ada is that the bounds of a range constraint are evaluated when the subtype declaration is elaborated. This means that the subtype declaration is equivalent to the following sequence:

today_now :  constant DAY  :=  TODAY;
subtype PAST is DAY range MON .. today_now;

where today_now represents an identifier not used elsewhere. The bounds of the subtype PAST are denoted by the subtype attributes PAST'FIRST (same as MON) and PAST'LAST (same as today_now).

Note that if the bounds of the range are not known at compilation time, the compiler will often need to generate (implicitly) a descriptor containing the value of the bounds. Hence, to minimize descriptor overhead, it is important to localize the knowledge about equivalent constraints in a single subtype declaration and then to use the name of this subtype, instead of repeating the constraint in several variable declarations.

Note also that, for reliability and maintainability, using a subtype is far better than repeating the corresponding constraint at various points of the text, since the value of an expression defining a bound may differ at these points. Thus it is preferable to write:

  subtype INDEX is INTEGER range K*M .. K*N;
  TABLE :  array (INDEX) of FLOAT;
  procedure UPDATE(X :  INDEX) is
  end UPDATE;
  for J in INDEX loop
    if TABLE(J) < TABLE(INDEX'LAST) then
    end if;
  end loop;

rather than to repeat the range K*M .. K*N at various points of the text or to use K*N directly (for INDEX'LAST).

In the case of the subprogram UPDATE the language does not even leave us this choice, since it requires a type or subtype name for subtype indications of formal parameters.

Address any questions or comments to adainfo@sw-eng.falls-church.va.us.