Tuesday, September 11, 2012

AdaTutor - Generics and Tasking

Generics

It would be easy to write a package that creates a single stack of 10 Integers, and lets us Push and Pop on it.  However, the code would be about the same regardless of the size of the stack, and regardless of the type of objects on the stack.  For example, a second package that creates a stack of 50 Dates would look about the same.  We can write one generic package, and instantiate it for any size and almost any type we need.  The specification is:

   generic
      Size : Positive;
      type Dummy is private;
   package Stack_Package is
      procedure Push(Object : in Dummy);
      function Pop return Dummy;
   end Stack_Package;

Since both Size and type Dummy are generic, both must be specified when we instantiate the package:

   package Stack_Of_10_Integers is new Stack_Package(10, Integer);
   package Stack_Of_50_Dates    is new Stack_Package(50, Date);

Generic objects like Size may be given default values.  A default value is used only if we don't specify that parameter in the instantiation.  For example:

generic
   Size : Positive := 10;
   type Dummy is private;
package Stack_Package is
   procedure Push(Object : in Dummy);
   function Pop return Dummy;
end Stack_Package;

package Stack_Of_10_Integers is new Stack_Package(Dummy => Integer);
package Stack_Of_50_Dates    is new Stack_Package(50, Date);

In the above example, we specified a generic type with

type Dummy is private;

Actually, there are a number of ways to specify a generic type.  How we specify the generic type determines what actual types we may use in the instantiation, and what we may do with objects of that type in the body of our generic package or subprogram.  Let's look at the alternatives.

If the generic part says type Dummy is private; then, in our subprogram or package body, what we may do with objects of type Dummy is similar to what we may do with objects of a private type outside an ordinary package: create them, assign them, test them for equality or inequality, and call other subprograms already available to us.  Although this list is short, we can instantiate with almost any type at all: Dates, Floats, Strings (see the next paragraph), Rainbow_Colors, and any type for which we can assign objects and test them for equality.  That means any type except a limited type.  We can't instantiate Stack_Package for type Ada.Text_IO.File_Type (a limited private type), or for task types (a limited type discussed later), because in our package body we're allowed to assign and test for equality objects of type Dummy.

We can't create objects of an unconstrained type like String.  So, in Ada 83, if our package or subprogram creates objects of type Dummy, we can't instantiate it for type String; we must use a constrained subtype of String.  Thus we could write subtype Name is String(1 .. 30); and then instantiate our package with package Stack_Of_50_Names is new Stack_Package(50, Name);.  In Ada 95 we can instantiate with an unconstrained type if and only if the formal type name in the generic part of the specification (Dummy in our example) is followed by (<>).  In that case, the subprogram or package body must initialize any objects of that type that it creates, in order to constrain them.

We can instantiate our package or subprogram even for limited types like Ada.Text_IO.File_Type if the generic part says type Dummy is limited private;.  However, the only things our package or subprogram body could do with objects of that type is create them and call other subprograms already available to us.  Ordinarily, that's not very useful.

If the generic part says type Dummy is (<>); then we can instantiate the package or subprogram for any discrete type.  That means any enumeration or integer type: Character, Boolean, Counter, No_Of_Apples, etc.  In the body, attributes like 'First are available.

If the generic part says type Dummy is range <>; then we can instantiate for any integer type.  In the body, we can do things that can be done with all integer types, such as add, etc.

If the generic part says type Dummy is digits <>; then we can instantiate for any floating point type, such as Float or the Real we created.

If the generic part says type Dummy is delta <>; then we can instantiate for any fixed point type, to be discussed in the section on More Records and Types.

In Ada 95, some additional forms are allowed; beginners shouldn't expect to grasp them until later.  If the generic part says type Dummy is tagged private; then we can instantiate with any tagged type, to be covered in the section on More Records and Types.

If the generic part says type Dummy is new X; or type Dummy is new X with private; then we can instantiate with type X or with any type derived from X.  If with private is included, then we must instantiate with a tagged type.

In both cases above, as with type Dummy is private; and type Dummy is limited private; we can follow Dummy with (<>) to allow instantiating with unconstrained types.  The body must initialize any objects of an unconstrained type that it creates.  We can also replace is in the generic part with is private to allow instantiating with abstract types, to be covered later.

If the generic part says type Dummy is mod <>; then we can instantiate with any modular type, to be covered in the section on More Records and Types.

Finally, if the generic part says with package Dummy is new P (<>); where P is the name of a generic package, then we can instantiate with any package that we obtained by instantiating P.

A generic package body looks just like an ordinary package body:

   package body Stack_Package is
      procedure Push(Object : in Dummy) is separate;
      function Pop return Dummy is separate;
   end Stack_Package;

   separate (Stack_Package)
   procedure Push(Object : in Dummy) is
      ...
   end Push;

   (etc.)

Note that we simplified our Stack_Package example by ignoring the possibilities of stack overflow and underflow.  Ordinarily, these would be handled by exceptions.

The specification of a generic subprogram must be given separately from the body.  For example, we can't eliminate the highlighted line in the following function:

   generic
      type Dummy_Float is digits <>;
      type Dummy_Vector is array (Integer range <>) of Dummy_Float;
   function Sum(V : in Dummy_Vector) return Dummy_Float;
   function Sum(V : in Dummy_Vector) return Dummy_Float is
      Answer : Dummy_Float := 0.0;
   begin
      for I in V'Range loop
         Answer := Answer + V(I);
      end loop;
      return Answer;
   end Sum;

We can instantiate Sum and call our instantiation as follows:

   type Vector is array(Integer range <>) of Float;
   V1 : Vector(1 .. 10);
   X  : Float;
   function Sumv is new Sum(Float, Vector);
   ...
   X := Sumv(V1);

Question

 generic
    type Dummy is range <>;
 procedure Display(Item : in Dummy);

 type No_Of_Apples is new Integer;
 type Counter is range 0 .. 1_000_000;
 type Answer is (Yes, No, Maybe);
 procedure Display_Apples   is new Display(No_Of_Apples);  -- 1
 procedure Display_Counters is new Display(Counter);       -- 2
 procedure Display_Answers  is new Display(Answer);        -- 3
Which commented line in the above program segment is illegal?

< prev   next >

No comments: