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

15.2 System Structures: Tagged Types

The variant records we studied in Chapter 12 provide much expressive power to create complex types with several different parts. However, they have an important limitation: A variant record must be fully defined and compiled, and CASE statements are used to control processing its various parts.

Now suppose a new variant must be added. Consider the employee record of Section 12.4, which we repeat here for clarity:

    TYPE PayCategories IS (Unknown, Professional, Sales, Clerical);
    
    TYPE Employee (PayStatus : PayCategories := Unknown) IS RECORD
      ID        : IDType;
      Name      : NameType;
      PayPeriod : Dates.Date;
    
      CASE PayStatus IS
        WHEN Professional =>
          MonthSalary : Currency.Quantity;
        WHEN Sales =>
          WeekSalary  : Currency.Quantity;
          CommRate    : CommissionPercentage;
          SalesAmount : Currency.Quantity;
        WHEN Clerical =>
          HourlyWage  : Currency.Quantity;
          HoursWorked : WorkHours;
        WHEN Unknown =>
          NULL;
      END CASE;
    
    END RECORD; 

Suppose a new category of employee is added, Manager, for example. The enumeration type PayCategories must be modified, and the variant type declaration must be changed, adding a choice WHEN Manager, to account for the new variant. All operations on objects of the type must be similarly changed.

Further, if the type declaration happened to appear in a package specification, every client of that package must at least be recompiled, and perhaps even modified. It would be nice if we could somehow extend a type, adding new fields and operations but without modifying or re-compiling existing packages or programs. This is called type extension and is provided in Ada by tagged types. A tagged type is analogous to a variant record, but it can be extended without changing the original type declaration.

Tagged Types

A record type can be declared as TAGGED to indicate that it will potentially be extended by adding additional fields.[1] Each object of a tagged type is given a tag by the compiler; you can think of a tag as analogous to a hidden discriminant. Whereas with ordinary variant records the programmer must write explicit code to use a discriminant, a tag is manipulated automatically in the executing program.

As an example of a tagged type, consider representing a person with three general characteristics: a name, a gender, and a date of birth. We can declare this as:

    TYPE Person IS TAGGED RECORD
      Name: NameType;
      Gender: Genders;
      BirthDate: Date;
    END RECORD;
where Genders has been declared as:
    TYPE Genders IS ( Female, Male);
and the name and birth date fields are, respectively, some string type and a date from our package Dates.

Now suppose we declare Person in the specification of a package Persons, together with a number of operations that are implemented in the package body. We then write one or more client programs that use Persons. At a later date, we discover a need to represent personnel, or persons working in a company. An employee is a person with a company identifier and a second date indicating when she joined the company. Note the "is a" relationship: An employee is a person with additional characteristics. Without tagged types, we'd either develop an entire new personnel type, or go back and modify our original person type. Using tagged types, we can derive a new type based on the existing one:

    TYPE Employee IS NEW Person WITH RECORD
      ID: IDRange;
      StartDate: Date;
    END RECORD;
This declares a new type and reflects the "is a" relationship directly. Each employee now has five fields: the two new ones and the three it inherited from the person type. Furthermore, the new type can be declared in a new package, with a new set of operations, without disturbing the existing package or any programs that use it. This technique is called programming by extension.

We can carry this further, of course. The payroll department in our company wishes to extend our employee type for payroll purposes and so needs three special categories of employees. The new types can be derived from the employee type:

    TYPE Professional IS NEW Employee WITH RECORD
      MonthSalary : Quantity;
    END RECORD;
    
    TYPE Sales IS NEW Employee WITH RECORD
      WeekSalary  : Quantity;
      CommRate    : CommissionPercentage;
    END RECORD;
    
    TYPE Clerical IS NEW Employee WITH RECORD
      HourlyWage  : Quantity;
    END RECORD;
where the Quantity values are taken from package Currency. In a further refinement of the "is a" relationship, a professional is an employee, who in turn is a person. As before, the new types can be declared and used in one or more new packages, without causing any modification of the older packages or any of their clients.

It is instructive to note that in Ada 83 new types can be derived from ordinary Ada 83 types. The new type has the same structure (set of values) as the original, and the operations of the original type are generally inherited by the new one. Ada 95 adds to this the ability to extend the type.

Converting Among Derived Tagged Types

The five types declared above form a type hierarchy:
    Person
      Employee
        Professional
        Sales
        Clerical
Ada allows us to convert explicitly from a lower type to a higher one. If P is a Person, E is an Employee, and R is a Professional, we can write an aggregate
    R := ( Name => "Nancy", 
           Gender => Female, 
           BirthDate => Date_Of(1950, Oct, 21),
           ID => 2345,
           StartDate => Date_Of(1990, Jul, 1),
           MonthSalary => 5000.00);
and can "up-convert" to P
    P := Person( R);
which is a familiar conversion construct. In the case of tagged types, the conversion "strips off" the extra fields.

How do we "down-convert?" Since a conversion to a lower type generally adds fields, we use a special aggregate structure for this. If we had

    P := (Name => "Nancy", 
          Gender => Female, 
          BirthDate => Date_Of(1950, Oct, 21);
we could make E by writing
    E := (P WITH ID => 2345, StartDate => Date_Of(1990, Jul, 1));
The text following WITH is called an extension aggregate. Generally, of course, client programs will not use the aggregate form because types like these will, in general, be PRIVATE. This brings us to the subject of operations on tagged types.

Primitive and Nonprimitive Operations on Tagged Types

The operations on tagged types are rather special. A fundamental Ada 95 notion is the primitive operation. Put simply, a primitive operation of a type is either a predefined operator on the type--like the operators on Integer, for example--or an operation (function, subprogram, or operator) that is declared just below the type in the same package specification and has a parameter of that type. Nearly all the operations in the packages thus far in this book have been, in Ada 95 terminology, primitive. The term becomes important in the context of tagged types. Each primitive operation of a tagged type T is inherited by all types derived from T; sometimes we desire the inheritance, but sometimes we do not.

We shall explain this in the context of three package specifications, Persons, Personnel, and Payroll, which appear as Program 15.1, Program 15.2, and Program 15.3 respectively.

Program 15.1
Specification for Persons

WITH Dates; USE Dates;
PACKAGE Persons IS
------------------------------------------------------------------------
--| Specification for Persons. This package provides a root type
--| Person, with the fields Name, Gender, and BirthDate. Person
--| is a tagged private type, which means that it has all the
--| characteristics of an ordinary private type but also that it
--| can be extended by derivation.
--| Author: Michael B. Feldman, The George Washington University 
--| Last Modified: November 1995                                     
------------------------------------------------------------------------
  
  TYPE Genders IS (Female, Male);
  
  SUBTYPE NameRange IS Positive RANGE 1..20;
  SUBTYPE NameType  IS String(NameRange); 

  TYPE Person IS TAGGED PRIVATE;
   
  -- selectors
  
  FUNCTION NameOf  (Whom: Person) RETURN NameType; 
  FUNCTION GenderOf(Whom: Person) RETURN Genders;
  FUNCTION DOBOf   (Whom: Person) RETURN Date;
  -- Pre:  Whom is defined
  -- Post: returns the appropriate field value
  
  PROCEDURE Put(Item: IN Person);
  -- Pre:  Item is defined
  -- Post: Item's fields are displayed
  
  PACKAGE Constructors IS
    
    -- this inner package is necessary so that MakePerson is not a
    -- "primitive" function, that is, so that it is not inherited
    -- by types derived from Person.
    
    FUNCTION MakePerson(Name     : String;
                        Gender   : Genders; 
                        BirthDate: Date) RETURN Person;
    -- Pre:  Name, Gender, and BirthDate are defined
    -- Post: returns a Person with the given field values
    
  END Constructors;  
  
PRIVATE
  
  TYPE Person IS TAGGED RECORD
    NameLength: NameRange := 1;
    NameField : NameType := (OTHERS => ' ');
    Gender    : Genders := Female;
    BirthDate : Date;
  END RECORD;

END Persons;

In Program 15.1, Person is a PRIVATE type with initialized fields, as in most of our packages. Note, in the visible part of the specification (above the PRIVATE line), the declaration

    TYPE Person IS TAGGED PRIVATE;
which is consistent with our understanding of private declarations, with the addition of TAGGED. The package specification further gives four operations in the selector category; this style is familiar to you from earlier packages. However, the constructor operation is not declared here, but rather in an inner package, Constructors. Why the unfamiliar structure?

Our intention in writing Persons is to allow new types to be derived and extended from Person. Consider the type Employee introduced earlier. An employee is a person with additional fields; the type Employee inherits all the primitive operations of Person, that is, for each primitive Person operation, there is a similar one for Employee, with a similar parameter profile. So the Employee type also has operations NameOf, GenderOf, and DOBOf.

Inheritance is fine for the selectors: For example, a client will certainly wish to find out an employee's name, and an inherited operation just like the Person selector is a perfectly good operation to return the name. The constructor is a different story, however, because we need to pass all the field values into it. A person has three fields; an employee has five. If we wrote a person constructor as a primitive operation, (e.g., MakePerson), it would be inherited by the employee type, so a client could call MakePerson with a parameter of type Employee. But this would be wrong! The object would be constructed with only three of its fields filled in!

Writing a separate constructor for Employee is a useful thing to do, and we shall do it shortly. However, it does not solve our problem because MakePerson would still be available for the client to call.

Because it would be very unsafe and therefore unwise to allow MakePerson to be inherited by derived types, we need to take preventive action. There are several ways to do this; here we handle the problem by realizing that--by Ada's rules of primitive operations--an operation declared in an inner package, such as Persons.Constructors in Program 15.1, is not primitive and is therefore not inherited. Putting the constructor in an inner package puts a small burden on the client programmer, who can write Persons.NameOf but must write Persons.Constructors.MakePerson. This is a small price to pay for the added safety.

Deriving New Tagged Types

Program 15.2 gives the specification for Personnel.

Program 15.2
Specification for Personnel

WITH Persons; USE Persons;
WITH Dates; USE Dates;
PACKAGE Personnel IS
------------------------------------------------------------------------
--| Specification for Personnel, which provides a type Employee,
--| a derivative of Persons.Person. Note that the operations on
--| objects of type Persons.Person are inherited by objects of
--| type Employee, so we need selectors only for the new
--| fields! As in the case of Persons, we place the constructor
--| in an inner package.
--| Author: Michael B. Feldman, The George Washington University 
--| Last Modified: September 1995                                     
------------------------------------------------------------------------
  
  TYPE Employee IS NEW Person WITH PRIVATE;
    -- Here is where Employee is derived; the extension fields are
    -- also PRIVATE, so clients cannot access them directly.
    
  TYPE IDType IS NEW Positive RANGE 1111..9999;
  
  -- selectors

  FUNCTION StartOf (Whom: Employee) RETURN Date;        
  FUNCTION IDOf    (Whom: Employee) RETURN IDType;
    -- Pre:  Whom is defined
    -- Post: return the appropriate field values
                      
  PROCEDURE Put(Item: Employee);
    -- Pre:  Item is defined
    -- Post: Item is displayed
  
  PACKAGE Constructors IS
  
    -- as in Persons, we use an inner package to prevent the
    -- constructor from being inherited by further derivatives
    -- of Employee
    
    FUNCTION  MakeEmployee(Name     : String;    
                           Gender   : Genders; 
                           BirthDate: Date; 
                           StartDate: Date; 
                           ID       : IDType) RETURN Employee;
    -- Pre:  Name, Gender, BirthDate, StateDate, and ID are defined
    -- Post: Whom contains the desired field values                      

  END Constructors;

PRIVATE

  TYPE Employee IS NEW Person WITH RECORD
    ID        : IDType := 1111;
    StartDate : Date;
  END RECORD;

END Personnel;

Its structure is similar to that of Persons, but note how the type Employee is declared:

    TYPE Employee IS NEW Person WITH PRIVATE;
The syntax WITH PRIVATE indicates a private extension; it allows Employee to be a PRIVATE type just as Person is. Personnel also provides selectors StartOf and IDOf, and a constructor MakeEmployee in an inner package.

The type Employee inherits the primitive operations of Person: NameOf, GenderOf, and DOBOf. This is fine; employees also have these fields. What about Put? Persons.Put displays the fields of a person. If Put were inherited by Employee, it would, of course, display only the fields that Employee and Person have in common, which is not what we desire. We therefore supply another Put for the employee type. Because it has a similar parameter profile, the only difference being the substitution of Employee for Person, this new employee operation is said to override the corresponding person operation. The body of Personnel.Put--we will show this shortly--displays all five fields of an employee.

Why were we able to override Person.Put so simply, without using an inner package? The key is that the two Put parameter profiles are so similar. The constructors' parameter profiles are very different from one another, so writing a MakePerson in Personnel, with a profile appropriate for Employee, simply would not have solved that problem.

Program 15.3 gives the specification for Payroll, which gives the three pay categories we sketched earlier.

Program 15.3
Specification for Payroll

WITH Currency; USE Currency;
WITH Dates; USE Dates;
WITH Persons; USE Persons;
WITH Personnel; USE Personnel;
PACKAGE Payroll IS
------------------------------------------------------------------------
--| Specification for Payroll, a set of payroll categories
--| derived from Personnel. Each type has a primitive operation
--| Put, which overrides the one inherited from Employee.
--| Author: Michael B. Feldman, The George Washington University 
--| Last Modified: September 1995                                     
------------------------------------------------------------------------

  SUBTYPE CommissionPercentage IS Float RANGE 0.00..0.50;

  TYPE Professional IS NEW Employee WITH PRIVATE;
  TYPE Sales IS NEW Employee WITH PRIVATE;
  TYPE Clerical IS NEW Employee WITH PRIVATE;

  PROCEDURE Put(Item: Professional);
  PROCEDURE Put(Item: Sales);
  PROCEDURE Put(Item: Clerical);

  PACKAGE Constructors IS
   
    -- constructors for the three new types
    FUNCTION MakeProfessional(Name       : String;    
                              Gender     : Genders;  
                              BirthDate  : Date; 
                              StartDate  : Date; 
                              ID         : IDType; 
                              MonthSalary: Quantity)
                              RETURN Professional;

    FUNCTION MakeSales (Name      : String; 
                        Gender    : Genders;  
                        BirthDate : Date;
                        StartDate : Date;  
                        ID        : IDType; 
                        WeekSalary: Quantity;
                        CommRate  : CommissionPercentage) 
                        RETURN Sales;

    FUNCTION MakeClerical (Name      : String; 
                           Gender    : Genders;  
                           BirthDate : Date;
                           StartDate : Date;  
                           ID        : IDType; 
                           HourlyWage: Quantity)
                           RETURN Clerical;

    -- Pre:  All input fields are defined 
    -- Post: Returns an initialized value of the respective type         

  END Constructors;
  
PRIVATE

  -- full extensions for the three types
  
  TYPE Professional IS NEW Employee WITH RECORD
    MonthSalary : Quantity;
  END RECORD;

  TYPE Sales IS NEW Employee WITH RECORD
    WeekSalary  : Quantity;
    CommRate    : CommissionPercentage;
  END RECORD;

  TYPE Clerical IS NEW Employee WITH RECORD
    HourlyWage  : Quantity;
  END RECORD; 

END Payroll;
The three types are closely related--all are used by the payroll department--so it is sensible to collect them into a single package as we have done here. Note the three derived PRIVATE type declarations, the three overriding Put operations, and the three constructors in the inner package. We have not included field selectors, preferring to leave that as an exercise.

Before going on to the package bodies, look at Program 15.4, which illustrates the use of these packages.

Program 15.4
Creating a Company of Employees

WITH Ada.Text_IO; USE Ada.Text_IO;
WITH Currency; USE Currency;
WITH Dates; USE Dates;
WITH Persons; USE Persons;
WITH Personnel; USE Personnel;
WITH Payroll; USE Payroll;
PROCEDURE Use_Payroll IS
------------------------------------------------------------------------
--| demonstrates the use of tagged types
--| Author: Michael B. Feldman, The George Washington University 
--| Last Modified: September 1995                                     
------------------------------------------------------------------------
  
  -- demonstrates the use of tagged types
  
  George: Person;
  Mary  : Employee;
  Martha: Professional;
  Virginia: Sales;
  Herman: Clerical;
  
BEGIN

  -- first construct all the people
  
  George := Persons.Constructors.MakePerson(
             Name      => "George",
             Gender    => Male,
             BirthDate => Date_Of(1971,Nov,2));

  Mary := Personnel.Constructors.MakeEmployee(
             Name      => "Mary",  
             Gender    => Female,
             BirthDate => Date_Of(1950,Oct,21),
             ID        => 1234,
             StartDate => Date_Of(1989,Jul,1));
 
  Martha := Payroll.Constructors.MakeProfessional(
             Name        => "Martha",
             Gender      => Female,
             BirthDate   => Date_Of(1947,Jul,8),
             ID          => 2222,
             StartDate   => Date_Of(1985,Jun,6),
             MonthSalary => MakeCurrency(50000.00));

  Virginia := Payroll.Constructors.MakeSales(
             Name       => "Virginia",
             Gender     => Female,
             BirthDate  => Date_Of(1955,Feb,1),
             ID         => 3456,
             StartDate  => Date_Of(1990,Jan,1),
             WeekSalary => MakeCurrency(2500.00),
             CommRate   => 0.25); 
 
  Herman := Payroll.Constructors.MakeClerical(
             Name       => "Herman",
             Gender     => Male,
             BirthDate  => Date_Of(1975,May,13),
             ID         => 1557,
             StartDate  => Date_Of(1991,Jul,1),
             HourlyWage => MakeCurrency(7.50));
  
  -- Now display them all. Note that each Put is a different
  -- primitive operation.
  
  Put(Item => George);
  Ada.Text_IO.Put_Line(Item => "------------------------");
  Put(Item => Mary);  
  Ada.Text_IO.Put_Line(Item => "------------------------");
  Put(Item => Martha);
  Ada.Text_IO.Put_Line(Item => "------------------------");
  Put(Item => Virginia);
  Ada.Text_IO.Put_Line(Item => "------------------------");
  Put(Item => Herman);  
  Ada.Text_IO.Put_Line(Item => "------------------------");
 
END Use_Payroll; 
Sample Run
Name: George
Gender: male
Birth Date: November 2, 1971
------------------------------
Name: Mary
Gender: female
Birth Date: October 21, 1950
ID Number: 1234
Start Date: July 1, 1989
------------------------------
Name: Martha
Gender: female
Birth Date: July 8, 1947
ID Number: 2222
Start Date: June 6, 1985

Category: Professional
Monthly Salary: 50000.00
------------------------------
Name: Virginia
Gender: female
Birth Date: February 1, 1955
ID Number: 3456
Start Date: January 1, 1990

Category: Sales
Weekly Salary: 2500.00
Commission Rate: 0.25
------------------------------
Name: Herman
Gender: male
Birth Date: May 13, 1975
ID Number: 1557
Start Date: July 1, 1991

Category: Clerical
Hourly Wage: 7.50
------------------------------

Each of the five variables is of a different type; in each case the appropriate constructor is called--an Ada compiler would reject an attempt to call an inappropriate one--and the appropriate Put is used to display the contents.

Bodies of the Tagged Type Packages

The bodies of Persons, Personnel, and Payroll are given as Program 15.5, Program 15.6, and Program 15.7, respectively.

Program 15.5
Body of Persons

WITH Ada.Text_IO;
WITH Ada.Integer_Text_IO;
WITH Dates.IO;
PACKAGE BODY Persons IS
------------------------------------------------------------------------
--| Body of Persons package 
--| Author: Michael B. Feldman, The George Washington University 
--| Last Modified: September 1995                                     
------------------------------------------------------------------------

  PACKAGE Gender_IO IS NEW Ada.Text_IO.Enumeration_IO(Enum => Genders);

  FUNCTION NameOf(Whom: Person) RETURN NameType IS
  BEGIN
    RETURN Whom.NameField;
  END NameOf; 

  FUNCTION GenderOf(Whom: Person) RETURN Genders IS
  BEGIN
    RETURN Whom.Gender;
  END GenderOf;

  FUNCTION DOBOf(Whom: Person) RETURN Date IS
  BEGIN
    RETURN Whom.BirthDate;  
  END DOBOf; 
 
  PROCEDURE Put(Item: Person) IS
  BEGIN
    Ada.Text_IO.Put(Item => "Name: ");
    Ada.Text_IO.Put(Item => Item.NameField(1..Item.NameLength));
    Ada.Text_IO.New_Line;

    Ada.Text_IO.Put(Item => "Gender: ");
    Gender_IO.Put(Item => Item.Gender, Set => Ada.Text_IO.Lower_Case);
    Ada.Text_IO.New_Line;

    Ada.Text_IO.Put(Item => "Birth Date: ");
    Dates.IO.Put(Item => Item.BirthDate, Format => Dates.IO.Full);
    Ada.Text_IO.New_Line;
  END Put;

  PACKAGE BODY Constructors IS
  
    FUNCTION MakePerson(Name     : String;
                        Gender   : Genders; 
                        BirthDate: Date) RETURN Person IS
      Temp: NameType;
    BEGIN -- MakePerson

      Temp(1..Name'Length) := Name;  -- copy into slice of Temp

      RETURN (NameLength => Name'Length,
              NameField => Temp,
              Gender    => Gender,
              BirthDate => BirthDate);
    END MakePerson;
  
  END Constructors;  

END Persons;   

Program 15.6
Body of Personnel

WITH Ada.Text_IO;
WITH Ada.Integer_Text_IO;
WITH Dates.IO;
PACKAGE BODY Personnel IS
------------------------------------------------------------------------
--| Body of Personnel package 
--| Author: Michael B. Feldman, The George Washington University
--| Last Modified: September 1995
------------------------------------------------------------------------

  PACKAGE BODY Constructors IS
    
    FUNCTION  MakeEmployee(Name     : String;
                           Gender   : Genders; 
                           BirthDate: Date;
                           StartDate: Date; 
                           ID       : IDType) RETURN Employee IS
    BEGIN
      -- note how the Persons constructor is used, with an
      -- aggregate for the Person fields and an
      -- extension aggregate to add in the extra fields.
      RETURN (Persons.Constructors.MakePerson(
        Name           => Name, 
        Gender         => Gender, 
        BirthDate      => BirthDate) 
        WITH 
          StartDate => StartDate, 
          ID => ID);
    END MakeEmployee;
    
  END Constructors;

  FUNCTION StartOf (Whom: Employee) RETURN Date IS
  BEGIN
    RETURN Whom.StartDate;
  END StartOf;
  
  FUNCTION IDOf (Whom: Employee) RETURN IDType IS
  BEGIN
    RETURN Whom.ID;
  END IDOf;

  PROCEDURE Put(Item: Employee) IS
  BEGIN 
    -- Note that we can convert Employee to Person and
    -- call Persons.Put for the common fields
    Persons.Put(Item => Persons.Person(Item));
    
    Ada.Text_IO.Put(Item => "ID Number: ");
    Ada.Integer_Text_IO.Put(Item => Positive(Item.ID), Width => 1);
    Ada.Text_IO.New_Line;

    Ada.Text_IO.Put(Item => "Start Date: ");
    Dates.IO.Put(Item => Item.StartDate, Format => Dates.IO.Full);
    Ada.Text_IO.New_Line;
  END Put;

END Personnel;

Program 15.7
Body of Payroll

WITH Ada.Text_IO;
WITH Ada.Float_Text_IO;
WITH Currency.IO;
PACKAGE BODY Payroll IS
------------------------------------------------------------------------
--| Body of Payroll package
--| Author: Michael B. Feldman, The George Washington University
--| Last Modified: September 1995
------------------------------------------------------------------------

  PACKAGE BODY Constructors IS
   
    -- constructors for the three new types
    
    FUNCTION  MakeProfessional (Name  : String;
                           Gender     : Genders; 
                           BirthDate  : Date;
                           StartDate  : Date; 
                           ID         : IDType;
                           MonthSalary: Quantity) 
                           RETURN Professional IS
    BEGIN
      RETURN  (Personnel.Constructors.MakeEmployee(
        Name             => Name,
        Gender           => Gender,
        BirthDate        => Birthdate,
        StartDate        => StartDate,
        ID               => ID) 
        WITH MonthSalary => MonthSalary);
    END MakeProfessional;
      
    FUNCTION MakeSales (Name      : String;
                        Gender    : Genders; 
                        BirthDate : Date;
                        StartDate : Date; 
                        ID        : IDType;
                        WeekSalary: Quantity;
                        CommRate  : CommissionPercentage) 
                        RETURN Sales IS
    BEGIN
      RETURN (Personnel.Constructors.MakeEmployee
        (Name           => Name,
        Gender          => Gender,
        BirthDate       => Birthdate,
        StartDate       => StartDate,
        ID              => ID) 
        WITH WeekSalary => WeekSalary, CommRate => CommRate);
    END MakeSales;

    FUNCTION MakeClerical (Name      : String;
                           Gender    : Genders; 
                           BirthDate : Date;
                           StartDate : Date; 
                           ID        : IDType;
                           HourlyWage: Quantity) 
                           RETURN Clerical IS
    BEGIN
      RETURN  (Personnel.Constructors.MakeEmployee
        (Name           => Name,
        Gender          => Gender,
        BirthDate       => Birthdate,
        StartDate       => StartDate,
        ID              => ID) 
        WITH HourlyWage => HourlyWage); 
    END MakeClerical;
    
  END Constructors;

  PROCEDURE Put(Item: Professional) IS
  BEGIN
    Put(Item => Employee(Item));
    Ada.Text_IO.New_Line;

    Ada.Text_IO.Put(Item => "Category: Professional");
    Ada.Text_IO.New_Line;
    Ada.Text_IO.Put(Item => "Monthly Salary: ");
    Currency.IO.Put(Item => Item.MonthSalary);    
    Ada.Text_IO.New_Line;
  END Put;

  PROCEDURE Put(Item: Sales) IS
  BEGIN
    Put(Item => Employee(Item));
    Ada.Text_IO.New_Line;

    Ada.Text_IO.Put(Item => "Category: Sales");
    Ada.Text_IO.New_Line;
    Ada.Text_IO.Put(Item => "Weekly Salary: ");
    Currency.IO.Put(Item => Item.WeekSalary);    
    Ada.Text_IO.New_Line;
    Ada.Text_IO.Put(Item => "Commission Rate: ");
    Ada.Float_Text_IO.Put(Item => Item.CommRate, Fore=>1,Aft=>2,Exp=>0);
    Ada.Text_IO.New_Line;
  END Put;

  PROCEDURE Put(Item: Clerical) IS
  BEGIN
    Put(Item => Employee(Item));
    Ada.Text_IO.New_Line;

    Ada.Text_IO.Put(Item => "Category: Clerical");
    Ada.Text_IO.New_Line;
    Ada.Text_IO.Put(Item => "Hourly Wage: ");
    Currency.IO.Put(Item => Item.HourlyWage);    
    Ada.Text_IO.New_Line;
  END Put;

END Payroll; 
Looking at Program 15.7, in the body of the constructor MakeEmployee we up-convert the employee to a person, then use MakePerson to fill in the person fields. Finally, we use an extension aggregate to fill in the remaining fields. Similarly, in the Put procedure, we up-convert as before and reuse the Persons.Put to display the person fields, and then we display the additional employee fields.

Variables of Tagged Types

Throughout this development, we have declared each variable to be of a specific tagged type. This is analogous to declaring constrained variant variables, as in Chapter 12. A plausible question is, then, whether there exists something analogous to unconstrained variant types and variables. The answer to the question is Yes, and we shall show how to do this in Section 15.4. A related question is whether, and how, we can create a "data base" or table of tagged records (e.g., an array of them). The answer to this question is also Yes, and the details are also shown in Section 15.4. Before we can complete the explanation, we must digress into presentation of a different variety of pointer type, namely, the general access type, which, like tagged types, is new in Ada 95.


[1] Tagged types are new in Ada 95; they do not exist in Ada 83.


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

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