Variant Records
So far, we have only seen static records
Static set of fields for each record
Sometimes it would be nice to vary some of the fields
Records of people share some properties
Name, SSN, etc.
Specific types of people may have more attributes
Employee: Salary, job-code
Customer: Customer Number

Variant Records (cont.)
Variant Records allow us to do this.
We can specify a Fixed Part, just like we learned before
We can specify a Varying Part, which we can define based on some criteria
Example: Employee
SUBTYPE NameRange IS Positive RANGE 1..20;
SUBTYPE NameType IS String( NameRange );
SUBTYPE IDType IS Positive 1111..9999;
SUBTYPE WorkHours IS Float RANGE 0.0..168.0;
SUBTYPE CommPct IS Float RANGE 0.00..0.50;
TYPE PayCat IS ( Unknown, Research, Sales, Staff );

TYPE Employee( PayStatus : PayCat :=
                        Unknown ) IS RECORD
  ID         : IDType;
  NameLength : NameRange;
  Name       : NameType;
  PayPeriod  : Dates.Data;
Example: Employee (cont.)
  CASE PayStatus IS
    WHEN Research =>
      MonthSalary : Currency.Quantity;
    WHEN Sales    =>
      WeekSalary  : Currency.Quantity;
      CommRate    : CommPct;
    WHEN Staff    =>
      HourlyWage  : Currency.Quantity;
      HoursWorked : WorkHours;
    WHEN Unknown  =>
      NULL;
  END CASE;
END RECORD;
Breaking it Down...
The Line:
TYPE Employee( PayStatus : PayCat :=
                        Unknown ) IS RECORD
Tells Ada that a discriminated Record follows.
PayStatus is the discriminant Field, which determines which variant fields to include.
The fixed part of the record always precedes the variant part.
Breaking it Down… (cont.)
Only One variant is accessible.
We can declare a variable of type Employee as follows:

Jerry  : Employee( PayStatus => Research );
Elaine : Employee( PayStatus => Sales );
George : Employee( PayStatus => Staff );
Accessing Fields
We can access the fields for the given type:
Jerry.ID := 2020;
Elaine.CommRate := 0.25;
George.HoursWorked := 40;
But accessing fields from other variants raises a CONSTRAINT_ERROR:
Jerry.CommRate := 0.20;
Elaine.HoursWorked := 20;
George.MonthSalary := 1000.00;
Unconstrained and Constrained Variables
We do not have to supply a default value for the discriminant field:
TYPE Employee( PayStatus : PayCat :=
                        Unknown ) IS RECORD
      versus
TYPE Employee2( PayStatus : PayCat ) IS RECORD
But if we do, then we can use any variable declared with the default value as being of multiple types.
Unconstrained and Constrained Variables (cont.)
  Bart  : Employee( PayStatus => Sales );
  Lisa  : Employee( PayStatus => Research );
  Homer : Employee;  -- Homer is of type “Unknown”

  Janeway : Employee2; -- Compile Error.
  Tuvok   : Employee2( PayStatus => Staff );
This comes in handy for arrays:
 
  TYPE EmployeeArray IS ARRAY( Positive RANGE <> )
      OF Employee;
  TheSimpsons : EmployeeArray( 1..3 );
 
Accessing Fields (cont.)
  FOR Simpson IN 1..3 LOOP
    Ada.Text_IO.Put(
             Item => TheSimpsons( Simpson ).Name );
    CASE TheSimpsons( Simpson ).PayStatus IS
      WHEN Research =>
         Currency.IO.Put(
                TheSimpsons( Simpson ).MonthSalary );
      WHEN Sales =>
         Currency.IO.Put(
                TheSimpsons( Simpson ).WeekSalary );
      WHEN OTHERS =>
         NULL;
    END CASE;
  END LOOP;
Accessing Fields (cont.)
This would NOT work for Employee2, because there is no default value for the discriminant field, so there is no way to declare a variable using the default.
Only variables declared with the default discriminant field value can be used in this way.
Example: Geometric Shapes
SUBTYPE NonNegFloat IS Float RANGE 0.0..Float’Last;
TYPE FigKind IS( Rectangle, Square, Circle );

TYPE Figure( FigShape : FigKind := Rectangle ) IS RECORD
  Area      : NonNegFloat := 0.0;
  Perimeter : NonNegFloat := 0.0;
  CASE FigShape IS
    WHEN Rectangle | Square =>
      Width  : NonNegFloat := 0.0;
      Height : NonNegFloat := 0.0;
    WHEN Circle =>
      Radius : NonNegFloat := 0.0;
  END CASE;
END RECORD;





Variant Records, Revisited
We have seen how Variant Records allow us to create structures that share some common attributes.
This works well, but any changes we make to the variant record require us to change a lot of code!
CASE-statements
IF-statements
Tagged Records
Tagged Records allow us to derive new records based on old ones.
We start with a more-general type, and move towards a more-specific type.
This is a part of the Object-Oriented Programming (OOP) paradigm.
C++
Java
Object-Oriented Programming
Four main features:
Encapsulation: Ada Packages provide this.
Genericity: Ada Generics provide this.
Inheritance: Ada Tagged Records provide this.
Polymorphism: Ada function overloading provides this.
All “OO” languages provide these in one form or another.
Example: Person

SUBTYPE NameRange IS Positive RANGE 1..20;
SUBTYPE NameType IS String( NameRange );
TYPE Genders IS( Female, Male );

TYPE Person IS TAGGED RECORD
  Name       : NameType;
  Gender     : Genders;
  BirthDate  : Date;
END RECORD;

Example: Person Æ Employee
AN employee is just a person with more attributes:

SUBTYPE IDType IS Positive 1111..9999;

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

One way to think about this is to use “is a”
Inheritance
Each variable of type Employee now has five fields:
Three from Person
Two from Employee
We can declare the new type (Employee) in a different package, with different operations.
This will leave all the code for Person intact.
There is no need to change any of the code that uses Person either.
Inheritance (cont.)
If we were to do this with variant records, we would have much more overhead.
Why?
Example: Person Æ Employee (cont.)
SUBTYPE CommPct IS Float RANGE 0.00..0.50;
SUBTYPE WorkHours IS Float RANGE 0.0..168.0;

TYPE Research IS NEW Employee WITH RECORD
  MonthSalary : Currency.Quantity;
END RECORD;

TYPE Sales IS NEW Employee WITH RECORD
  WeekSalary  : Currency.Quantity;
  CommRate    : CommPct;
END RECORD;

TYPE Staff IS NEW Employee WITH RECORD
  HourlyWage  : Currency.Quantity;
  HoursWorked : WorkHours;
END RECORD;
Again...
These types can be implemented in a separate package, leaving the Employee package, and all programs that use it, untouched.
This gives us a Type Hierarchy:
Person
  Employee
    Research
    Sales
    Staff
Assignment
We can use aggregates:
P : Person;
E : Employee;
R : Research;
R := ( Name => “Ally”,
       Gender => Female,
       BirthDate => MakeDate( 1965, 10, 21 ),
       ID => 2345,
       StartDate => MakeDate( 1994, 7, 1 ),
       MonthSalary => 5000.00 );
Accessing Fields
And we can cast:
Up-convert (strips off extra fields):

P := Person( R );

Down-convert (add extra fields in):

E := ( P WITH ID => 2345, StartDate =>
              MakeDate( 1994, 7, 1 ) );

Primitive Operations
Ada uses the notion of Primitive Operations to mean:
The built-in ones:
  =
  :=
The ones defined within a package specification:
Put( Item : Integer )
Get( )
Inherited Operations
Primitive Operations are inherited by all subclasses of tagged records.
This is very handy in most cases.

FUNCTION NameOf( Whom : Person ) RETURN NameType;
FUNCTION GenderOf( Whom : Person ) RETURN Genders;
FUNCTION DOBOf( Whom : Person ) RETURN Date;
PROCEDURE PUT( Item : Person );

These are nice to have, even for an Employee.
Inherited Operations (cont.)
For Constructors, it might be dangerous:

FUNCTION MakePerson( Name   : String,
                     Gender : Genders,
                     DOB    : Date ) RETURN Person;

A user may call this for an Employee type, leaving the Employee-specific variables unset!
The solution is to not allow constructors to be inherited.
Limiting Inheritance
Ada does not “pass down” things defined in Inner Packages, so we simply put constructors in an inner package.
By doing this, the MakePerson function is not accessible in derived types.
This changes the way a client of the Persons package calls functions, but gives us better security.