Showing posts with label record. Show all posts
Showing posts with label record. Show all posts

Tuesday, September 18, 2012

AdaTutor - More Records and Types (2)

Tagged Records and Dynamic Dispatching

In Ada 95, a record may be tagged.  This allows us later to derive a new type from the original and add fields.  For example, if Day_Of_Week_Type is suitably declared along with Day_Subtype and Month_Type, Ada 95 lets us write

   type Date is tagged record
      Day   : Day_Subtype;
      Month : Month_Type;
      Year  : Integer;
   end record;

   type Complete_Date is new Date with record
      Day_Of_Week : Day_Of_Week_Type;
   end record;
Now objects of type Date have three fields: Day, Month, and Year, but objects of type Complete_Date have four fields: Day, Month, Year, and Day_Of_Week.

We can derive a new type without adding any fields by writing, for example,

   type New_Date is new Date with null record;

If a tagged record is to be a private type, the declaration in the package specification contains the words tagged private, like this:

   type Date is tagged private;

The actual structure of the record is declared in the private part of the package specification exactly as any other tagged record:

   private
      ...
      type Date is tagged record
         Day   : Day_Subtype;
         Month : Month_Type;
         Year  : Integer;
      end record;
This is called a private tagged type.

Ada 95 also has private extensions.  With the package specification segment shown below, the structure of type Date, with the fields Day, Month, and Year, is available outside the package.  However, the fact that a Complete_Date contains a Day_Of_Week field is not:

      type Date is tagged record
         Day   : Day_Subtype;
         Month : Month_Type;
         Year  : Integer;
      end record;
      type Complete_Date is new Date with private;
      ...
   private
      ...
      type Complete_Date is new Date with record
         Day_Of_Week : Day_Of_Week_Type;
      end record;

Ada 95 tagged types and Ada 95 hierarchical libraries both help support Object Oriented Design (OOD).

We can convert objects of a derived type to the parent type.  For example, if we have Cd : Complete_Date; we can write Date(Cd).  This is useful in making software reusable.  For example, suppose we have written a procedure to Display a Date:

   procedure Display(D : in Date);
If we now wish to write a procedure to Display a Complete_Date, we could call on the procedure we've already written.  If the above procedure and the associated types are defined in package P, we could write:
   with Ada.Text_IO, P; use P;
   procedure Display(Cd : in Complete_Date) is
      -- Call previous procedure to display the first 3 fields.
      Display(Date(Cd));
      -- Now display the fourth field.
      Ada.Text_IO.Put_Line(Day_Of_Week_Type'Image(Cd.Day_Of_Week));
   end Display;

Thus the procedure to display a Date was reused when we wrote the procedure to display a Complete_Date.

Although type Date and the types derived from it are all different types, we can refer to all the types collectively as Date'Class.  The attribute 'Class can be applied to the name of any tagged type.  If we declare an object to be of type Date'Class, we must initialize it so that the compiler will know the exact type of the object.  The type of this object can't change at run time.  For example, if we write D : Date'Class := Date'(12, Dec, 1815); we can't later write D := Complete_Date'(4, Jul, 1776, Thu);.  This would change the type of the object D and make it grow larger by adding a new field, Day_Of_Week.  This isn't permitted.

However, we can declare an access type to Date'Class without being specific about the exact type to be accessed:

type Ptr is access Date'Class;

Now objects of type Ptr can access objects of type Date at one time and objects of type Complete_Date at another.  Also, we could build a linked list, with some of the links of type Date and some of type Complete_Date.  This is called a heterogeneous linked list.

Let's suppose that we declare, in the specification for a package P, type Date (a tagged record), type Complete_Date derived from Date with an extra field, and two overloaded procedures Display, one to display a Date and one to display a Complete_Date.  Suppose that the specification for P also includes the definition for type Ptr:

type Ptr is access Date'Class;

Now suppose that our program, which withs and uses P, declares an array A of two elements of type Ptr.  Let A(1) access a Date and A(2) access a Complete_Date.  The program to follow shows how the system will decide at run time which of two overloaded versions of Display to call, depending on the type of A(I).all:

   package P is
      ...
      type Date is tagged record ...
      type Complete_Date is new Date with record ...
      type Ptr is access Date'Class;
      procedure Display(D : in Date);
      procedure Display(D : in Complete_Date);
   end P;

   with P; use P;
   procedure Dynamic_Dispatching_Demo is
      A : array(1 .. 2) of Ptr;
   begin
      A(1) := new Date'(12, Dec, 1815);
      A(2) := new Complete_Date'(4, Jul, 1776, Thu);
      for I in A'Range loop
         -- Decides at run time which Display to call.
         Display(A(I).all);
      end loop;
   end Dynamic_Dispatching_Demo;

This is called dynamic dispatching or tagged type dispatching, and is available only in Ada 95, not in Ada 83, because Ada 83 doesn't have tagged records.

In Ada 95, the attributes 'Class and 'Tag can be used in tests.  To illustrate this, we need a slightly more complicated example:

   type Employee is tagged record
      Name : String(1 .. 5);
   end record;

   type Hourly_Employee is new Employee with record
      Hours_Per_Week : Integer;
   end record;

   type Salaried_Employee is new Employee with record
      Days_Vacation : Integer;
   end record;

   type Manager is new Salaried_Employee with record
      Num_Supervised : Integer;
   end record;
Here we created a hierarchy of tagged types as follows:
   type Employee ... 
   type Hourly_Employee is new Employee ...
   type Salaried_Employee is new Employee ...
   type Manager is new Salaried_Employee ...
             Employee
                /\
               /  \
              /    \
 Hourly_Employee  Salaried_Employee
                        |
                        |
                     Manager
Note that Employee'Class includes all four types above, while Salaried_Employee'Class includes only Salaried_Employee and Manager.  The attribute 'Class means the type named and all the types below it in the hierarchy.

Now let's declare an array of four elements that access objects of types in Employee'Class:

   type P is access Employee'Class;
   A : array(1 .. 4) of P;

Finally, let's let each element of our array access a different type in our hierarchy:

A(1) := new Employee'(Name => "Alice");
A(2) := new Hourly_Employee'(Name => "Bob  ",
                             Hours_Per_Week => 40);
A(3) := new Salaried_Employee'(Name => "Carol",
                               Days_Vacation => 10);
A(4) := new Manager'(Name => "Dave ", Days_Vacation => 5,
                     Num_Supervised => 3);

Then the test A(1).all'Tag = A(2).all'Tag will be False, because A(1).all and A(2).all have different types.  The test A(4).all in Salaried_Employee'Class will be True, because type Manager is in Salaried_Employee'Class.  Finally, the test A(4).all in Hourly_Employee'Class will be False, because Hourly_Employee'Class contains only type Hourly_Employee, not type Manager.

The test for tag equality or inequality (e.g., A(1).all'Tag = A(2).all'Tag) requires that we with and use Ada.Tags, a package that comes with Ada 95.

In Ada 95, the formal parameters ("dummy arguments") of a subprogram may also be of a class-wide type.  For example, in a package that has defined both of our Display procedures and the associated types, we can write:

   procedure Show(X : in Date'Class) is
   begin
     Display(X);  -- Decides at run time which Display to call.
   end Show;

It this case, the exact type of X is not known until run time, and can vary from one call of Show to the next.  Therefore, the statement Display(X); will decide at run time which version of Display to call.

Related to this are access parameters in Ada 95.  Instead of writing

   procedure Show(X : in Date'Class);
we can write
   procedure Show(X : access Date'Class);

In this case, Show is no longer called with an object belonging to Date'Class (that is, a Date or a Complete_Date), but rather with an object of any access type that accesses objects belonging to Date'Class.  For example, we could now call Show with an object of the type Ptr we defined earlier:

   D : Ptr := new Complete_Date(4, Jul, 1776, Thu);
   Show(D);

Show again decides at run time which version of Display to call, but now Show must dereference the access value passed to it:

   procedure Show(X : access Date'Class) is
   begin
      Display(X.all);  -- Decides at run time which Display to call.
   end Show;

The accessibility check which we discussed earlier when we talked about access types has to be performed at run time in the case of access parameters.  The Ada 95 compiler automatically generates code to do this.  For access types accessing named objects, discussed earlier, the accessibility check is performed at compile time.  In both cases the purpose of the accessibility check is to insure that no object of an access type can ever access an object that no longer exists.

Here's our sample tagged type dispatching program, rewritten to make use of access parameters:

   package P is
      ...
      type Date is tagged record ...
      type Complete_Date is new Date with record ...
      type Ptr is access Date'Class;
      procedure Display(D : in Date);
      procedure Display(D : in Complete_Date);
      -- Body calls Display(X.all);
      procedure Show(X : access Date'Class);
   end P;

   with P; use P;
   procedure Dynamic_Dispatching_Demo is
      A : array(1 .. 2) of Ptr;
   begin
      A(1) := new Date'(12, Dec, 1815);
      A(2) := new Complete_Date'(4, Jul, 1776, Thu);
      for I in A'Range loop
         Show(A(I));
      end loop;
   end Dynamic_Dispatching_Demo;

Again, the for loop first displays a Date and then displays a Complete_Date.

< prev   next >

Monday, September 17, 2012

AdaTutor - More Records and Types

Record Discriminants and Record Variants

The definition of a record type can have discriminants, which have the same form as formal parameters ("dummy arguments") of subprograms, except that the mode is omitted.  Default values may be supplied.  For example,

   type Matrix is array(Integer range <>, Integer range <>)
      of Float;
   type Square_Matrix(Size : Positive := 9) is record
      Sq : Matrix(1 .. Size, 1 .. Size);
   end record;

Although objects of type Matrix can be rectangular, objects of type Square_Matrix must be square.  In declaring these objects, we use the same syntax as a subprogram call, with either positional or named notation:

   A : Square_Matrix(7);  -- a 7-by-7 matrix
   B : Square_Matrix(Size => 5);  -- a 5-by-5 matrix
   C : Square_Matrix;  -- a 9-by-9 matrix, using the default
                       -- value of Size

Of course, subtypes of discriminated records can be declared. For example,

subtype Chess_Board is Square_Matrix(Size => 8);

The older Ada 83 RM used a record discriminant in its definition of type Text for the Text_Handler package specification of section 7.6:

   Maximum : constant := ... ;
   subtype Index is Integer range 0 .. Maximum;
   ...
   type Text(Maximum_Length : Index) is record
      Pos   : Index := 0;
      Value : String(1 .. Maximum_Length);
   end record;

With the simplified version of type Text that we presented earlier, every object of type Text occupied enough memory for the longest string we expected to handle (e.g., 80 characters).  With this version, each object of type Text that we create can have just the Maximum_Length we need, and its effective length Pos can vary from zero to that Maximum_Length.  For example, we could declare Name : Text(50); and Employee_Number : Text(9).

The definition of a record type can have a variant, which also has the same form as a subprogram formal parameter without the mode.  However, the syntax of a case construct is used to specify part of the record.  Although a record can have several discriminants, it can have only one variant, and the variant part must appear last in the record.  For example,

   type Sex_Type is (Male, Female);
   type Person(Sex : Sex_Type) is record
      Age : Natural;
      case Sex is
         when Male =>
            Bearded  : Boolean;
         when Female =>
            Children : Natural;
      end case;
   end record;

If the sex of the person is Male, we want the record to include a Boolean showing whether he's bearded, but if the sex is Female, we want the record to include an Integer (subtype Natural), showing the number of children she has.

Objects are declared and given values as we'd expect:

   John : Person(Sex => Male) := (Sex => Male, Age => 21,
                                  Bearded => False);
   Mary : Person(Sex => Female) := (Sex => Female, Age => 18,
                                    Children => 0);

Attempting to reference John.Children or Mary.Bearded will raise Constraint_Error.  Subtypes may be declared as follows:

   subtype Man   is Person(Male);
   subtype Woman is Person(Female);

Question

 type Computer_Size is (Handheld, Notebook, Desktop,
                        Mainframe, Cluster);
 type Computer(Size : Computer_Size) is record
   MB_Mem : Positive;
   Disks  : Natural;
   case Size is
   when Cluster =>
      Number_Of_Units : Positive;
      Data_Rate       : Float;
   when others =>
      null;
   end case;
 end record;
 My_PC       : Computer(Desktop) := (Desktop, MB_Mem =>  64,
                                     Disks => 2);  -- 1
 Company_LAN : Computer(Cluster) := (Cluster, MB_Mem => 512,
                                     Disks => 8);  -- 2
Which commented declaration above is illegal?

< prev   next >

Saturday, August 18, 2012

AdaTutor - Records and Arrays (1)

Records

A record lets us group several declarations together and refer to them as one:

   type Month_Type is (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug,
                       Sep, Oct, Nov, Dec);
   subtype Day_Subtype is Integer range 1 .. 31;
   type Date is
    record
      Day   : Day_Subtype;   -- Naturally, Day_Subtype and
      Month : Month_Type;    -- Month_Type must be defined
      Year  : Integer;       -- before type Date is defined.
    end record;
   USA : Date;

In this example, USA has three parts (called "fields"): USA.Day, USA.Month, and USA.Year.  The fields of a record can be of any type, including other records.  Here USA.Day is of type Integer, USA.Month is of type Month_Type, and USA.Year is of type Integer.  The fields of USA may be referenced separately:

      USA.Day   := 4;
      USA.Month := Jul;
      USA.Year  := 1776;

However, USA can also be referenced as one object.  For example, we could have assigned all three fields in one statement: USA := (4, Jul, 1776);.  Here the object on the left is of type Date, and the object on the right is called an aggregate.  The aggregate fits the type Date because it contains an Integer, a Month_Type, and another Integer.  This aggregate is said to use positional notation because its three parts appear in the same order as the three parts of the record definition: first Day, then Month, then Year.

We can specify the parts of an aggregate in any order if we use named notation:USA := (Month => Jul, Day => 4, Year => 1776);.  (The symbol => is read "arrow" and may not contain a space.)  Using named notation often improves program readability, especially if the names of the fields are well chosen.

We can switch from positional to named notation in an aggregate.  But once we use named notation, the compiler loses track of position, so we can't switch back to positional.  For example, the following is legal:

      USA := (4, Year => 1776, Month => Jul);

But the following is illegal because positional notation can't follow named:

      USA := (4, Year => 1776, Jul); -- illegal

Record discriminants, record variants, and tagged records will be discussed in the section on More Records and Types.

Question

  procedure Record_Exercise is
    type Month_Type is (Jan, Feb, Mar, Apr, May, Jun,
                        Jul, Aug, Sep, Oct, Nov, Dec);
    subtype Day_Subtype is Integer range 1 .. 31;
    type Date is
      record
        Day   : Day_Subtype;
        Month : Month_Type;
        Year  : Integer;
      end record;
    D1, D2, D3 : Date;  -- 1
  begin
    D1       := (Month => Jan, Day => 22, Year => 1983);  -- 2
    D2.Day   := 22;
    D2.Month := 1;  -- 3
    D2.Year  := 1983;
    D3       := D2;  -- 4
  end Record_Exercise;
Which commented line in the above program is illegal?
1
2
3
4

Arrays

To declare an array in Ada, we specify the type and range of the subscript, followed by the type of the elements of the array.  The subscript can have any discrete type (integer or enumeration), and the elements of the array can be of any type at all, including records and other arrays.  There are three ways to declare an array in Ada.  Here are three examples of the most direct, but least flexible, way (types Rainbow_Color and Date must be defined earlier):

 A : array(Rainbow_Color range Orange .. Blue) of Date;
    -- A four-element array, each element of which is a record
    -- with three parts.  The allowable subscripts are Orange,
    -- Yellow, Green, and Blue.  Here A(Yellow) is of type Date,
    -- and A(Yellow).Year is of type Integer.
 B : array(Integer range -10 .. 10) of Integer;
    -- An array of 21 Integers, with Integer subscripts.
 C : array(0 .. 30) of Float;
    -- Here (0 .. 30) is understood to mean
    -- (Integer range 0 .. 30), and we have an array of 31
    -- Floats, with Integer subscripts.

A subscript can be an expression; if I is an Integer, we can write C(2*I). If a subscript is out-of-range (for example, A(Red) or C(-32)), the program will raise a Constraint_Error.

This direct method of declaring arrays is usually used to create single arrays for table lookup, etc., where there's no need to have several arrays of the same type.  A better way to declare an array is to specify a type name for the array itself. Then several objects can be declared to have that same type.  For example,

      type Vector100 is array(1 .. 100) of Float;
      type Vector300 is array(1 .. 300) of Float;
      D, E, F : Vector100;
      G, H    : Vector300;

Here D, E, and F are all of type Vector100, so we can write D := E; and assign the entire array with one statement.  Similarly, we can write G := H;, but not G := F;, because G and F have different types.

An even more flexible way to declare arrays is to leave the range of the subscript unspecified with the box symbol, <>, specifying the range when declaring the objects.  For example,

      type Vector is array(Integer range <>) of Float;
      D, E, F : Vector(1 .. 100);
      G, H    : Vector(1 .. 300);

There are two errors to avoid when declaring arrays in this way.  One is to declare a type with the box symbol for the range of the subscript, and then fail to specify the range when declaring a variable:

      type Vector is array(Integer range <>) of Float;
      D1 : Vector; -- illegal

This error is called unconstrained array.  Unconstrained arrays are legal in formal parameters ("dummy arguments") of procedures and functions, and a function can return an unconstrained array type.  (We'll learn about these things later.)  But an unconstrained array is illegal when declaring a variable, because the compiler needs to know the range of the subscript.

In Ada 95, we may constrain the variable array by initializing it:

      D1 : Vector := (2.3, 4.5, 4.0); -- legal in Ada 95 only

Here the compiler assumes the range of the subscript to be 1 .. 3. In Ada 83, however, we must specify the subscript range of a variable explicitly:

      D1 : Vector(1 .. 3) := (2.3, 4.5, 4.0); -- legal

In both Ada 83 and Ada 95, a constant array, which must be initialized, is automatically constrained.  Therefore, the following is legal in both Ada 83 and Ada 95, and the compiler will assume the subscript range to be 1 .. 3:

     D1 : constant Vector := (2.3, 4.5, 4.0); -- legal 

The other error to avoid when declaring arrays in this way is to specify the range of the subscript twice: once when declaring the type and once when declaring the object:

      type Vector100 is array(1 .. 100) of Float;
      D2 : Vector100(1 .. 100);  -- illegal

Even if the two ranges agree, this is illegal and is called doubly constrained array.

Arrays may be initialized and assigned with aggregates, and both positional and named notation may be used.  For example, arrays A and B are equal here:

    type Vector5 is array(1 .. 5) of Float;
    A : constant Vector5 := (2.0, 4.0, 8.0, 16.0, 32.0);
    B : constant Vector5 := (1 => 2.0, 2 => 4.0, 3 => 8.0,
                             4 => 16.0, 5 => 32.0);

The aggregate must fill the whole array, but the reserved word others is available.  Here's an array of 500 Float variables, all initialized to 0.0:

    type Vector500 is array(1 .. 500) of Float;
    V1 : Vector500 := (others => 0.0);

If others follows named notation, it's best to qualify the aggregate with the type name.  Here W(10) = 1.3, W(15) = -30.7, and the rest of the array is 0.0:

    W : Vector500 := Vector500'(10 => 1.3,  15 => -30.7,
                                others => 0.0);

Sometimes we must qualify an aggregate when others is used with named notation; at other times it's optional.  The rules ( Ada 95 RM section 4.3.3, paragraphs 10-15) are complicated.  It's easiest always to qualify an aggregate when others follows named notation, as shown above.

In array aggregates, multiple choices can be denoted with the vertical bar (|), shift-backslash on PC keyboards.  In this array, the elements with odd subscripts are True, while the elements with even subscripts are False:

    type Decade is array(1 .. 10) of Boolean;
    D1 : Decade;
    ...
    D1 := Decade'(1 | 3 | 5 | 7 | 9 => True,  others => False);

Here we assigned to D1 with an executable statement for variety; we could also have initialized D1 in the declarative region with the same aggregate.  Some people read the vertical bar as "and," others as "or."  One can say, "Elements 1, and 3, and 5, and 7, and 9 are True," or one can say, "If the subscript is 1, or 3, or 5, or 7, or 9, the array element is True."


Question

 with Ada.Text_IO, Ada.Integer_Text_IO;
 use Ada.Text_IO, Ada.Integer_Text_IO;
 procedure Array_Quiz is
   subtype Unaccented_Cap_Letter is Character range 'A' .. 'Z';
   type Set_Of_Unaccented_Letters is
      array(Unaccented_Cap_Letter) of Boolean;
   Vowels : Set_Of_Unaccented_Letters :=
       Set_Of_Unaccented_Letters'('A'|'E'|'I'|'O'|'U' => True,
                                  others => False);
   Letter : Unaccented_Cap_Letter;
 begin
   Letter := 'E';
   Put(Boolean'Pos(Vowels(Letter)));
 end Array_Quiz;
What will this program display?
  1. The program will display 1.
  2. The program will display 2.
  3. The program will display TRUE.
  4. The program will display E.

In an array declaration or an array type declaration, we may totally omit the range of the subscript (not even supplying a box), if the type or subtype of the subscript has a reasonable number of possible values.  (We did that in the declaration of type Set_Of_Unaccented_Letters in the last question.)  For example, in

  type Rainbow_Color is (Red, Orange, Yellow, Green, Blue, Indigo,
                         Violet);
  R : array(Rainbow_Color) of Float;

we have an array of seven Floats, because there are seven possible values of type Rainbow_Color.  Similarly,

    V : array(Character) of Boolean;
creates an array of 256 Booleans (128 in Ada 83).  However, J : array(Integer) of Float; is an attempt to declare a very large array indeed!  In a case like this, we can use a subtype in the subscript declaration.  The following creates an array of 31 Floats:
    subtype Day_Subtype is Integer range 1 .. 31;
    J : array(Day_Subtype) of Float;

The attributes 'First and 'Last, which we've seen with discrete types, can also be used with array types and with the array names themselves.  For example,

    type My_Vector is array(30 .. 33) of Integer;
    Mv : My_Vector;
    ...
    Mv(30) := 1000;
    Mv(31) := 20;
    Mv(32) := -70;
    Mv(33) := -500;

Here Mv'First and My_Vector'First are both 30.  Mv'Last and My_Vector'Last are both 33.  Note that 'First and 'Last refer to the subscripts, not to the values of the array elements.  Thus Mv'First is not 1000.  To obtain the value of the first element, we would use Mv'First as a subscriptMv(Mv'First) is 1000.

The attribute 'Range is an abbreviation for 'First .. 'Last.  It can be used with array types and array names, and (in Ada 95 only), with scalar types.  Thus Mv'Range and My_Vector'Range both mean 30 .. 33.  In Ada 95, we can write Integer'Range as an abbreviation for Integer'First .. Integer'Last.  The attribute 'Length is also available: Mv'Length and My_Vector'Length are both 4.

< prev   next >