Previous | Next | Table of Contents | Index | Program List | Copyright

5.6 Data Structures: Subtypes of Scalar Types

One of the most important features of Ada is that it permits the declaration of data types. Many of these data types will be discussed in later chapters. In this section we will discuss the declaration of subtypes. Recall that a type is a set of values and a set of operations appropriate for those values. A subtype defines a subset of the values associated with the original type, or base type; the operations of the subtype are the same as those of the base type.

A scalar type is one each of whose values consists of a single component. All the types we have seen so far, except for strings, are in this category. Composite types, whose values may consist of several components, are introduced in Chapter 8. In this section we shall consider how to create subranges of the predefined scalar types Integer, Float, and Character and of programmer-defined enumeration types like the names of the days in the week. Subtypes are used both to make a program more readable and to enable detection of an attempt to give a variable a value that is unreasonable in the problem environment.

Subtypes of Predefined Scalar Types

So far in this book we have used a number of different subtypes. The first two are predefined in the Ada language and are thus always available:

    SUBTYPE Natural IS Integer RANGE 0..Integer'Last;
    SUBTYPE Positive IS Integer RANGE 1..Integer'Last;
    

The three subtypes introduced in Section 3.6, are predefined, not in the language itself, but in the standard package Ada.Calendar, and are therefore available to any program with a context clause WITH Ada.Calendar:

    SUBTYPE Year_Number IS Integer RANGE 1901..2099;
    SUBTYPE Month_Number IS Integer RANGE 1..12;
    SUBTYPE Day_Number IS Integer RANGE 1..31;

In Section 2.6 we introduced a programmer-defined subtype,

    SUBTYPE NonNegFloat IS Float RANGE 0.0 .. Float'Last;

Finally, we presented the subtypes Width and Depth in Section 3.7.

    SUBTYPE Depth IS Integer RANGE 1..Screen_Depth;
    SUBTYPE Width IS Integer RANGE 1..Screen_Width;

All these subtypes (and subtypes in general) have a common characteristic. An attempt to assign to a variable a value that is not in the defined set of values causes a compilation error or warning, if the compiler can detect the attempt. If the compiler cannot detect the attempt--for example, because the out-of-range value is not computed until the program is executed--the compiler builds a check into your program to ensure that Constraint_Error is raised if, during execution, the value is indeed out of range.

Example 5.7

Subtype declarations begin with the reserved word SUBTYPE. Two subtypes are declared below, as well as some variables.

    SUBTYPE SmallInt IS Integer RANGE -50..50;
    SUBTYPE CapitalLetter IS Character RANGE 'A'..'Z'; 

X, Y, Z : SmallInt; NextChar : CapitalLetter; InDay : Calendar.Month_Number; Hours_Worked : NonNegFloat;

The first subtype, SmallInt, is a subtype with base type Integer. The following sequence of assignment statements will cause Constraint_Error to be raised at run time:

    X := 26;
    Y := 25;
    Z := X + Y;

Why is there no compilation error? Remember that the compiler does not actually carry out the computation you specify; it only produces an object program, which carries out the computation when it is executed. Even though it may be obvious to you that this simple computation will produce an out-of-range result, it is not obvious to the compiler, so the checking can be done, and the exception raised, only at run time.

CapitalLetter has the base type Character. Any character from A to Z inclusive may be stored in a variable of type CapitalLetter. Constraint_Error will be raised if an attempt is made to store any other character in a variable of type CapitalLetter. For example, the assignment statement

    NextChar := 'a';
causes the exception to be raised because the character 'a' is not included in the subtype CapitalLetter. The compiler might notice this attempted out-of-range assignment, but instead of considering this an outright error, it will often give a warning stating that the statement will cause Constraint_Error to be raised at run time.

Month_Number is a subtype with base type Integer. A variable of type Month_Number may be used to keep track of the current date, a value between 1 and 31 inclusive. The statement

    Ada.Integer_Text_IO.Get(Item => InDay); 
reads a data value into InDay. Constraint_Error is raised if the data value entered from the keyboard is less than 1 or greater than 31. This is clearly a situation where the compiler cannot guess whether the value entered from the keyboard will be in range or not.

Subtypes of Enumeration Types

Subtypes of programmer-defined types can be defined just as easily as subtypes of predefined types. As an example, consider the month-name type introduced in Section 3.6:

    TYPE Months IS
        (January, February, March, April, May, June,
        July, August, September, October, November, December);

Now we can define subtypes for three seasons as follows:

    SUBTYPE Spring IS Months RANGE March .. May;
    SUBTYPE Summer IS Months RANGE June .. August;
    SUBTYPE Autumn IS Months RANGE September .. November;

We cannot easily define a subtype Winter (the months December, January, and February) because unfortunately Ada requires that the values of a subtype be specified in the form of a range and therefore contiguous, that is, adjacent in the base type definition. Sometimes a way can be found to work around this, as in the case of the day-names type introduced in Section 4.6:

    TYPE Days IS
        (Mon, Tue, Wed, Thu, Fri, Sat, Sun);

Since Mon through Fri are contiguous, and Sat and Sun are contiguous, we can define subtypes for weekdays and weekend days:

    SUBTYPE Weekdays IS Days RANGE Mon .. Fri;
    SUBTYPE Weekend  IS Days RANGE Sat .. Sun;

However, this work-around requires the Days type to look different from the "normal" American calendar in which the week starts on Sunday.

SYNTAX DISPLAY
Subtype Declaration

Form:
SUBTYPE subtype-name IS
    base-type-name RANGE minvalue ..
    maxvalue; 

Example:
SUBTYPE Uppercase IS Character RANGE 'A'..'Z';

Interpretation:
A new subtype named subtype-name is defined. A variable of type subtype-name may be assigned a value from minvalue through maxvalue inclusive. The values minvalue and maxvalue must belong to the base type, and minvalue must be less than maxvalue.

PROGRAM STYLE
Motivation for Using Subtypes

You may be wondering why we bother with subtypes. They don't seem to provide any new capabilities. However, they do provide additional opportunity for your program to "bomb" because attempting to store an out-of-range value in a variable causes an exception, usually Constraint_Error, to be raised. This should happen only as the result of an error by either the programmer or the program user.

The use of subtypes ensures the immediate detection of an out-of-range value. This contributes to a program's reliability and usefulness because it ensures that variables do not acquire values that are meaningless in the problem being solved (such as a negative number of hours worked in a week).

In this book we use subtypes extensively, especially where it is necessary that a variable be nonnegative.

Compatibility Rules for Types and Subtypes

Ada does not allow a programmer accidentally to mix the types of operands for an operator. This means that the expression V1 + V2 leads to a compilation error such as "type incompatible operands" if V1 is one data type (say Integer) and V2 is another (say Float). However, what if V1 is type Integer and V2 is type SmallInt (a subtype of Integer)? In this case, the expression is valid because SmallInt and Integer are considered compatible types. Ada has simple compatibility rules: Two values are compatible if they have the same type name or one value's type is a subtype of the other value's type (Integer and SmallInt, for example) or if their types are subtypes of the same base type (Positive and SmallInt, for example).

The compatibility relationship between operands determines what operators can be used with the operands. An operator can be used only with operands that are compatible with it and with each other. Assignment of a value to a variable is possible only if the value and the variable are compatible. And an actual parameter supplied to a function or procedure must be compatible with the corresponding formal parameter.

These rules ensure, for example, that a Float value is not assigned to an Integer variable, that an Integer value is not assigned to a Float variable, and that an Integer value is not supplied to Ada.Text_IO.Put (which expects a character). On the other hand, a Positive value can be supplied to Ada.Integer_Text_IO.Put (which expects an Integer) because of the subtype relationship.

Type Membership: The Operator IN

An important operator that applies to almost all types in Ada is IN. It can be used to determine whether a given value is a member of a given type's set of values.

Example 5.8

Suppose Today is of type Days, and that we have defined the two subtypes Weekdays and Weekend as above. The following IF statement serves as an example of the use of IN:

    IF Today IN Weekdays THEN
       Ada.Text_IO.Put(Item => "Another day, another dollar...");
    ELSE
       Ada.Text_IO.Put(Item => "We've worked hard, let's play hard!");
    END IF;

Program 5.13 can be used to determine whether we need to go to work tomorrow. It is based on Program 4.4. Notice the use of the IF statement shown above.

Program 5.13
Do We Have to Work Tomorrow?

WITH Ada.Text_IO;
PROCEDURE Work_Days IS
------------------------------------------------------------------------
--| Demonstrates the use of enumeration subtypes:
--| prompts user for a day of the week and determines whether
--| the following day is a weekday or weekend day.
--| Author: Michael B. Feldman, The George Washington University
--| Last Modified: July 1995                       
------------------------------------------------------------------------

  TYPE Days IS (Mon, Tue, Wed, Thu, Fri, Sat, Sun);
  SUBTYPE WeekDays IS Days RANGE Mon .. Fri;
  SUBTYPE WeekEnd  IS Days RANGE Sat .. Sun;
  PACKAGE Day_IO IS
     NEW Ada.Text_IO.Enumeration_IO (Enum => Days);

  Today     : Days;
  Tomorrow  : Days;

BEGIN -- Work_Days

  -- prompt user to enter a day abbreviation
  Ada.Text_IO.Put (Item => "Enter a 3-letter day of the week > ");
  Day_IO.Get (Item => Today);

  -- find tomorrow
  IF Today = Days'Last THEN
    Tomorrow := Days'First;
  ELSE
    Tomorrow := Days'Succ(Today);
  END IF;

  Ada.Text_IO.Put (Item => "Tomorrow is ");
  Day_IO.Put (Item => Tomorrow);
  Ada.Text_IO.New_Line;

  -- Is Tomorrow a week day or a weekend day?
  IF Tomorrow IN Weekdays THEN
    Ada.Text_IO.Put (Item => "Another day, another dollar...");
    Ada.Text_IO.New_Line;
  ELSE
    Ada.Text_IO.Put (Item => "We've worked hard, let's play hard!");
    Ada.Text_IO.New_Line;
  END IF;

  Ada.Text_IO.Put (Item => "Have a good day tomorrow.");
  Ada.Text_IO.New_Line;

END Work_Days;
Sample Run
Enter a 3-letter day of the week > fri
Tomorrow is SAT
We've worked hard, let's play hard!
Have a good day tomorrow.

As you have seen in this chapter, another use for IN is in counting loops. So far, you have seen only loops whose range is 1..repetitions. Another useful form of the counting loops is to give the name of a type or subtype as the range of the loop. Suppose that SmallInt is defined as above, with a range -50..50, then

    FOR Counter IN SmallInt LOOP
       Ada.Integer_Text_IO.Put(Item => Counter);
       Ada.Text_IO.New_Line;
    END LOOP;

displays all the values in the type SmallInt (-50, -49, -48...), one at a time.

Example 5.9

Program 5.14 displays the addition table for integer values between 0 and 9 (type SmallNat). For example, the table line beginning with the digit 9 shows the result of adding to 9 each of the digits 0 through 9. The initial FOR loop prints the table heading, which is the operator + and the list of digits from 0 through 9.

The nested FOR loops are used to print the table body. The outer FOR loop (loop counter Left) first prints the current value of Left. In the inner FOR loop, each value of Right (0 through 9) is added to Left and the individual sums are printed. Each time the outer loop is repeated 10 additions are performed; a total of 100 sums are printed.

Program 5.14
Addition Table

WITH Ada.Text_IO;
WITH Ada.Integer_Text_IO;
PROCEDURE Addition_Table IS
------------------------------------------------------------------------
--| Displays an addition table.
--| Author: Michael B. Feldman, The George Washington University
--| Last Modified: July 1995
------------------------------------------------------------------------

  MaxDigit : CONSTANT Natural := 9;            -- largest digit
  SUBTYPE SmallNatural IS Natural RANGE 0 .. MaxDigit;

BEGIN  -- Addition_Table

  -- Display the table heading.
  Ada.Text_IO.Put(Item => "+");
  FOR Right IN SmallNatural LOOP               -- Display heading
    Ada.Integer_Text_IO.Put(Item => Right, Width => 3);
  END LOOP;
  Ada.Text_IO.New_Line;                        -- Terminate heading

  -- Display the table body.
  FOR Left IN SmallNatural LOOP

    -- Display each row of the table
    Ada.Integer_Text_IO.Put(Item => Left, Width => 1);

    FOR Right IN SmallNatural LOOP
      Ada.Integer_Text_IO.Put(Item => Left + Right, Width => 3);
    END LOOP;

    Ada.Text_IO.New_Line;                      -- Terminate table row

  END LOOP;

END Addition_Table;
Sample Run
+  0  1  2  3  4  5  6  7  8  9
0  0  1  2  3  4  5  6  7  8  9
1  1  2  3  4  5  6  7  8  9 10
2  2  3  4  5  6  7  8  9 10 11
3  3  4  5  6  7  8  9 10 11 12
4  4  5  6  7  8  9 10 11 12 13
5  5  6  7  8  9 10 11 12 13 14
6  6  7  8  9 10 11 12 13 14 15
7  7  8  9 10 11 12 13 14 15 16
8  8  9 10 11 12 13 14 15 16 17
9  9 10 11 12 13 14 15 16 17 18

Example 5.10

Program 5.15 shows how this structure can be used to print all the days, weekdays, and weekend days in the week. This program uses three FOR loops, one for the base type Days and one for each of the two subtypes.

Program 5.15
Illustrating Enumeration Subtypes

WITH Ada.Text_IO;
PROCEDURE Display_Days IS
------------------------------------------------------------------------
--| Display the days of the week, weekdays, weekend days;
--| demonstrate enumeration subtypes and how they can be used
--| to control a loop
--| Author: Michael B. Feldman, The George Washington University
--| Last Modified: July 1995                       
------------------------------------------------------------------------

  TYPE Days IS (Mon, Tue, Wed, Thu, Fri, Sat, Sun);
  SUBTYPE WeekDays IS Days RANGE Mon .. Fri;
  SUBTYPE WeekEnd  IS Days RANGE Sat .. Sun;
  PACKAGE Day_IO IS
     NEW Ada.Text_IO.Enumeration_IO (Enum => Days);

BEGIN -- Display_Days

  Ada.Text_IO.Put (Item => "The days of the week are ");
  FOR Day IN Days LOOP
    Day_IO.Put (Item => Day, Width => 4);
  END LOOP;
  Ada.Text_IO.New_Line;

  Ada.Text_IO.Put (Item => "The weekdays are ");
  FOR Day IN WeekDays LOOP
    Day_IO.Put (Item => Day, Width => 4);
  END LOOP;
  Ada.Text_IO.New_Line;

  Ada.Text_IO.Put (Item => "The weekend days are ");
  FOR Day IN WeekEnd LOOP
    Day_IO.Put (Item => Day, Width => 4);
  END LOOP;
  Ada.Text_IO.New_Line;

END Display_Days;
Sample Run
The days of the week are MON TUE WED THU FRI SAT SUN 
The weekdays are MON TUE WED THU FRI 
The weekend days are SAT SUN 

Program 5.16 is a modification of Program 5.15, using REVERSE in the FOR loop statement to show how the days can be displayed in reverse order.

Program 5.16
Displaying Subtypes in Reverse Order

WITH Ada.Text_IO;
PROCEDURE Reverse_Display_Days IS
------------------------------------------------------------------------
--| display the days of the week, weekdays, weekend days;
--| demonstrate enumeration subtypes and how they can be used
--| to control a loop running in reverse.
--| Author: Michael B. Feldman, The George Washington University
--| Last Modified: July 1995
------------------------------------------------------------------------

  TYPE Days IS (Mon, Tue, Wed, Thu, Fri, Sat, Sun);
  SUBTYPE WeekDays IS Days RANGE Mon .. Fri;
  SUBTYPE WeekEnd  IS Days RANGE Sat .. Sun;
  PACKAGE Day_IO IS
     NEW Ada.Text_IO.Enumeration_IO (Enum => Days);

BEGIN -- Reverse_Display_Days

  Ada.Text_IO.Put (Item => "The days of the week are ");
  FOR Day IN REVERSE Days LOOP
    Day_IO.Put (Item => Day, Width => 4);
  END LOOP;
  Ada.Text_IO.New_Line;

  Ada.Text_IO.Put (Item => "The weekdays are ");
  FOR Day IN REVERSE WeekDays LOOP
    Day_IO.Put (Item => Day, Width => 4);
  END LOOP;
  Ada.Text_IO.New_Line;

  Ada.Text_IO.Put (Item => "The weekend days are ");
  FOR Day IN REVERSE WeekEnd LOOP
    Day_IO.Put (Item => Day, Width => 4);
  END LOOP;
  Ada.Text_IO.New_Line;

END Reverse_Display_Days;
Sample Run
The days of the week are SUN SAT FRI THU WED TUE MON 
The weekdays are FRI THU WED TUE MON 
The weekend days are SUN SAT 

Example 5.11

Program 5.17 uses the Screen package from Chapter 3 to draw vertical and horizontal lines on the screen, dividing the screen into four quadrants. We repeat the subtype and constant declarations from Screen here, just to remind you:

    Screen_Depth : CONSTANT Integer := 24;
    Screen_Width : CONSTANT Integer := 80;
    
    SUBTYPE Depth IS Integer RANGE 1..Screen_Depth;
    SUBTYPE Width IS Integer RANGE 1..Screen_Width;

The loop

    FOR Count IN Screen.Width LOOP
        Screen.MoveCursor (Row => 12, Column => Count);
        Ada.Text_IO.Put (Item => '-');
        Screen.MoveCursor 
        	(Row => 13, Column => (Screen.Screen_Width - Count) + 1);
        Ada.Text_IO.Put (Item => '-');
    END LOOP;

draws the horizontal separator consisting of two lines of hyphen characters on rows 12 and 13 of the screen. The parameters to the first call of Screen.MoveCursor move the cursor one position to the right in each loop iteration; just to make the program more interesting, the second call moves the cursor one position to the left each time.

Program 5.17
Dividing the Screen into Four Quadrants

WITH Ada.Text_IO;
WITH Screen;
PROCEDURE Four_Pieces IS
------------------------------------------------------------------------
--| This program divides the screen into four pieces by drawing
--| horizontal and vertical lines. The Screen package is used to
--| position the cursor.
--| Author: Michael B. Feldman, The George Washington University
--| Last Modified: July 1995
------------------------------------------------------------------------

BEGIN -- Four_Pieces

  Screen.ClearScreen;
  
  FOR Count IN Screen.Depth LOOP
    Screen.MoveCursor (Row => Count, Column => 41);
    Ada.Text_IO.Put (Item => '|');
    Screen.MoveCursor 
      (Row => (Screen.Screen_Depth - Count) + 1, Column => 42);
    Ada.Text_IO.Put (Item => '|');
  END LOOP;

  FOR Count IN Screen.Width LOOP
    Screen.MoveCursor (Row => 12, Column => Count);
    Ada.Text_IO.Put (Item => '-');
    Screen.MoveCursor 
      (Row => 13, Column => (Screen.Screen_Width - Count) + 1);
    Ada.Text_IO.Put (Item => '-');
  END LOOP;

  Screen.MoveCursor (Row => 24, Column => 1);

END Four_Pieces;

SYNTAX DISPLAY
Counting Loops (Type-Name Form)

Forms:
FOR counter IN type-name LOOP 
    statement sequence
END LOOP;
FOR counter IN REVERSE type-name LOOP 
    statement sequence
END LOOP;

Example:
FOR WhichDay IN Weekdays LOOP
    Day_IO.Put (Item => WhichDay);
    Ada.Text_IO.New_Line;
END LOOP;

Interpretation:
The number of times statement sequence is executed is determined by the number of values in the type given by type-name, which must be the name of an integer or enumeration type or subtype. The value of the loop counter counter is set to type-name'First before the first execution of statement sequence; counter is incremented to its successor value after each execution of statement sequence; the last execution of statement sequence occurs when counter is equal to type-name'Last.The value of counter must not be changed within statement sequence. The variable counter is not declared separately and has no existence outside the loop.

If REVERSE is present, counter is initialized to type-name'Last and the iteration is done backward, decrementing counter to its predecessor value after each execution of statement sequence.

Limitations of the FOR Statement

The FOR statement is very powerful and useful, but it has one important limitation: the loop counter is always either incremented (by taking the successor) or decremented (by taking the predecessor). The FOR statement can therefore be used only to loop through all the values of a given range. There is no way to count by 2s, for example.

Ada provides two other loop statements, which can be used with arbitrary loop control conditions, not just counting straight through the values of a range. Specifically, we can use either the WHILE loop or the general loop structure, which will be presented in Chapter 6.


Previous | Next | Table of Contents | Index | Program List | Copyright

Copyright © 1996 by Addison-Wesley Publishing Company, Inc.