Showing posts with label generic. Show all posts
Showing posts with label generic. Show all posts

Tuesday, September 25, 2012

AdaTutor - Advanced Topics (5)

Subprogram Parameters with Generics

The generic part of a subprogram or package can specify a dummy subprogram as well as a dummy type or object.  This is similar to using subprograms as parameters in Algol and Pascal, and to using the little-known keyword EXTERNAL in Fortran.  In Ada, we simply precede the dummy subprogram specification with the keyword with in the generic part.  This use of the word with has nothing to do with context clauses.  Here's the specification of a generic function that has one dummy function specification in the generic part:

generic
   with function Dummy(X : in Float) return Float;
function Definite_Integral(Lower_Limit, Upper_Limit : in Float)
   return Float;

If we instantiate Ada.Numerics.Generic_Elementary_Functions for type Float, and use that instantiation, we get functions like Cos for type Float.  We could then instantiate Definite_Integral for Cos, and make use of it as follows:

 Answer : Float;
 function Definite_Integral_Of_Cos is new Definite_Integral(Cos);
 ...
 Answer := Definite_Integral_Of_Cos(Lower_Limit => 0.0,
                                    Upper_Limit => 1.5708);

generic
   with function Dummy(X : in Float) return Float;
function Definite_Integral(Lower_Limit, Upper_Limit : in Float)
   return Float;

function Definite_Integral(Lower_Limit, Upper_Limit : in Float)
   return Float is
   Mult : array(0 .. 6) of Float := (1.0, 4.0, 2.0, 4.0,
                                     2.0, 4.0, 1.0);
   Sum  : Float := 0.0;
   X    : Float;  -- the independent variable
begin
   for I in 0 .. 6 loop
      X   := Lower_Limit
             + (Float(I) / 6.0) * (Upper_Limit - Lower_Limit);
      Sum := Sum + Mult(I) * Dummy(X);
   end loop;
   return Sum * (Upper_Limit - Lower_Limit) / 18.0;
end Definite_Integral;
This is one possible body for the generic function Definite_Integral.  (We've repeated the specification above the body for reference.)  This function integrates the function Dummy between the two limits by evaluating Dummy at seven points and using Simpson's rule.  (Definite_Integral could be improved by making the number of points a generic parameter, instead of fixing it at seven.)

< prev   next >

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 >

Saturday, August 11, 2012

AdaTutor - Generic Instantiation

Displaying Integers

with Ada.Text_IO;                               with Ada.Text_IO;
use  Ada.Text_IO;                               use  Ada.Text_IO;
procedure Hello is       THIS          THIS     procedure Add is
begin                 <= IS              IS =>  begin
  Put_Line("Hello!");    RIGHT        WRONG       Put_Line(2 + 2);
end Hello;                                      end Add;
Now let's write a program called Add that computes 2 + 2 and displays the result.  You may think that we could take the Hello program and substitute 2 + 2 for "Hello!", but that won't work.  (We never said Ada is easy!)  Why won't it work?  Because Ada.Text_IO doesn't have a procedure Put_Line that takes an Integer parameter.  One correct program is this:
with Ada.Text_IO; use Ada.Text_IO;
procedure Add is
  package My_Int_IO is new Integer_IO(Integer); use My_Int_IO;
begin
  Put(2 + 2);
  New_Line;
end Add;
The package Ada.Text_IO contains procedures for type String.  This package is ready-to-use.  However, inside Ada.Text_IO is another package, Integer_IO, that's not ready-to-use.
        Ada.Text_IO
 
 Put_Line for type String  
 Put      for type String  
 Get_Line for type String   
 Get      for type String   
 New_Line                   
 ...                        
                            
 ______ Integer_IO ______   
 | Put for type <>      |   
 | Get for type <>      |   
 | ...                  |   
 |______________________|   
 
It's called a generic package because it has an empty box (<>) in place of the type.  We can make a new, ready-to-use package from Integer_IO by giving the type: package My_Int_IO is new Integer_IO(Integer); we could have used any name in place of My_Int_IO.  Note that we've declared our new package locally inside the procedure Add.  My_Int_IO now has the same procedures and functions as Integer_IO, but with the empty box filled in with the type Integer.

Since Integer_IO (and therefore My_Int_IO) doesn't have a Put_Line, we call My_Int_IO.Put and then Ada.Text_IO.New_Line.  Note that our program says use My_Int_IO; after declaring My_Int_IO.  When the compiler sees the call Put(2 + 2); it writes code to call My_Int_IO.Put rather than Ada.Text_IO.Put, because My_Int_IO.Put takes a parameter of type Integer.  The compiler then finds New_Line in Ada.Text_IO and writes a call to Ada.Text_IO.New_Line.

Question


True or False?  In our example, a program could call Integer_IO.Put.


Generic Instantiation

       package My_Int_IO is new Integer_IO(Integer);

    ______ Integer_IO ______    ______ My_Int_IO _______
    | Put for type <>      |    | Put for type Integer |
    | Get for type <>      |    | Get for type Integer |
    | ...                  |    | ...                  |
    |______________________|    |______________________|

This process of creating an instance of the generic package Integer_IO for the type Integer is called generic instantiation.  Later in this course we'll learn to write our own generic packages, procedures, and functions.  However, we wanted to learn, early in the course, how to instantiate an already written generic package in order to display integers.

But why does Ada make Ada.Text_IO ready-to-use for Strings, while Integer_IO is generic, and has to be instantiated for Integers?  Because programs normally use only one type String, but can have several integer types.  Right now, we know of only one integer type, the standard Integer.  Later we'll learn about user-defined types and derived types, and we'll see how there can be many integer types.  Integer_IO can be instantiated for each of these types.

Ada 95 provides a ready-to-use package Ada.Integer_Text_IO, which is equivalent to the package we obtain by instantiating Ada.Text_IO.Integer_IO for the type Integer.  Thus, with an Ada 95 compiler, the above program wouldn't have to do its own instantiation of Integer_IO; instead, it could have added Ada.Integer_Text_IO to both the with and the use clause.  However not all Ada 83 compilers provide such a package, and those that do use a non-standard name, such as IIO.

There's another generic package within Ada.Text_IO called Float_IO.  Input and output of floating point numbers is done by instantiating Float_IO for the type Float.  As with integers, we'll later learn how there can be several floating point types besides the standard Float; that's why Ada.Text_IO.Float_IO is generic.  (Ada 95 provides us with a ready-to-use package called Ada.Float_Text_IO, equivalent to the package we obtain by instantiating Ada.Text_IO.Float_IO for the type Float.  However, not all Ada 83 compilers provide a similar package, and those that do use a non-standard name like FIO.)

Later in this course, when we cover attributes, we'll learn another way to display Integers (and, in Ada 95, Floats as well), by using the 'Image attribute.

We're now ready for our first Outside Assignment!  Let's compile and run our two sample programs, Hello and Add.  The files HELLO.ADA and ADD.ADA are already supplied for you, and they're written to work with Ada 83 compilers as well as Ada 95.  (That's why they say simply Text_IO instead of Ada.Text_IO.)

< prev   next >